diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 000000000000..5339c1e3fa65 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,63 @@ +name: "CodeQL" + +on: + push: + branches: [ '5.6' ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ '5.6' ] + schedule: + - cron: '34 11 * * 4' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'java' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + queries: +security-and-quality + + + # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + # ℹī¸ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + + # If the Autobuild fails above, remove it and uncomment the following three lines. + # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. + + # - run: | + # echo "Run, Build Application using script" + # ./location_of_script_within_repo/buildscript.sh + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + with: + category: "/language:${{matrix.language}}" \ No newline at end of file diff --git a/.github/workflows/contributor-build.yml b/.github/workflows/contributor-build.yml index e6ccfdb63ed3..426e6aebaacb 100644 --- a/.github/workflows/contributor-build.yml +++ b/.github/workflows/contributor-build.yml @@ -1,4 +1,4 @@ -# The main CI of Hibernate ORM is https://ci.hibernate.org/job/hibernate-orm-6.0-h2-main/. +# The main CI of Hibernate ORM is https://ci.hibernate.org/job/hibernate-orm-5.6-h2/. # However, Hibernate ORM builds run on GitHub actions regularly # to check that it still works and can be used in GitHub forks. # See https://docs.github.com/en/free-pro-team@latest/actions @@ -13,35 +13,41 @@ on: pull_request: branches: - '5.6' + +permissions: {} # none + +# See https://github.com/hibernate/hibernate-orm/pull/4615 for a description of the behavior we're getting. +concurrency: + # Consider that two builds are in the same concurrency group (cannot run concurrently) + # if they use the same workflow and are about the same branch ("ref") or pull request. + group: "workflow = ${{ github.workflow }}, ref = ${{ github.event.ref }}, pr = ${{ github.event.pull_request.id }}" + # Cancel previous builds in the same concurrency group even if they are in process + # for pull requests or pushes to forks (not the upstream repository). + cancel-in-progress: ${{ github.event_name == 'pull_request' || github.repository != 'hibernate/hibernate-orm' }} + jobs: build: + permissions: + contents: read name: Java 8 runs-on: ubuntu-latest - # We want to know the test results of all matrix entries - continue-on-error: true strategy: fail-fast: false matrix: - # When GitHub Actions supports it: https://github.com/actions/toolkit/issues/399 - # We will use the experimental flag as indicator whether a failure should cause a workflow failure include: - rdbms: h2 - experimental: false +# - rdbms: hsqldb - rdbms: derby - experimental: true + - rdbms: mysql8 - rdbms: mariadb - experimental: true - - rdbms: postgresql - experimental: true + - rdbms: postgresql_9_5 + - rdbms: postgresql_13 - rdbms: oracle - experimental: true - rdbms: db2 - experimental: true - rdbms: mssql - experimental: true + - rdbms: sybase # Running with HANA requires at least 8GB memory just for the database, which we don't have on GH Actions runners # - rdbms: hana -# experimental: true steps: - uses: actions/checkout@v2 with: @@ -77,7 +83,7 @@ jobs: run: ./ci/build-github.sh shell: bash - name: Upload test reports (if Gradle failed) - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 if: failure() with: name: test-reports-java8-${{ matrix.rdbms }} @@ -85,45 +91,4 @@ jobs: ./**/target/reports/tests/ ./**/target/reports/checkstyle/ - name: Omit produced artifacts from build cache - run: ./ci/before-cache.sh - build11: - name: Java 11 - runs-on: ubuntu-latest - # We want to know the test results of all matrix entries - continue-on-error: true - steps: - - uses: actions/checkout@v2 - with: - persist-credentials: false - - name: Set up Java 11 - uses: actions/setup-java@v1 - with: - java-version: 11 - - name: Get year/month for cache key - id: get-date - run: | - echo "::set-output name=yearmonth::$(/bin/date -u "+%Y-%m")" - shell: bash - - name: Cache Maven local repository - uses: actions/cache@v2 - id: cache-maven - with: - path: | - ~/.m2/repository - ~/.gradle/caches/ - ~/.gradle/wrapper/ - # refresh cache every month to avoid unlimited growth - key: maven-localrepo-${{ steps.get-date.outputs.yearmonth }} - - name: Run build script - run: ./ci/build-github.sh - shell: bash - - name: Upload test reports (if Gradle failed) - uses: actions/upload-artifact@v2 - if: failure() - with: - name: test-reports-java11 - path: | - ./**/target/reports/tests/ - ./**/target/reports/checkstyle/ - - name: Omit produced artifacts from build cache - run: ./ci/before-cache.sh + run: ./ci/before-cache.sh \ No newline at end of file diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 000000000000..b3c0111f1481 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,380 @@ +/* + * 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 groovy.transform.Field +import io.jenkins.blueocean.rest.impl.pipeline.PipelineNodeGraphVisitor +import io.jenkins.blueocean.rest.impl.pipeline.FlowNodeWrapper +import org.jenkinsci.plugins.workflow.support.steps.build.RunWrapper + +/* + * See https://github.com/hibernate/hibernate-jenkins-pipeline-helpers + */ +@Library('hibernate-jenkins-pipeline-helpers@1.5') _ +import org.hibernate.jenkins.pipeline.helpers.job.JobHelper + +@Field final String NODE_PATTERN_BASE = 'Worker&&Containers' +@Field List environments + +this.helper = new JobHelper(this) + +helper.runWithNotification { +def defaultJdk = '8' +stage('Configure') { + this.environments = [ +// buildEnv(defaultJdk, 'h2'), +// buildEnv(defaultJdk, 'hsqldb'), +// buildEnv(defaultJdk, 'derby'), +// buildEnv(defaultJdk, 'mysql8'), +// buildEnv(defaultJdk, 'mariadb'), +// buildEnv(defaultJdk, 'postgresql_9_5'), +// buildEnv(defaultJdk, 'postgresql_13'), +// buildEnv(defaultJdk, 'oracle'), +// buildEnv(defaultJdk, 'db2'), +// buildEnv(defaultJdk, 'mssql'), +// buildEnv(defaultJdk, 'sybase'), +// buildEnv(defaultJdk, 'hana', 'HANA'), +// buildEnv(defaultJdk, 's390x', 's390x'), +// buildEnv(defaultJdk, 'tidb', 'tidb', 'tidb_hibernate@pingcap.com'), + // Disable EDB for now as the image is not available anymore +// buildEnv(defaultJdk, 'edb') + jdkBuildEnv(defaultJdk, '11'), + jdkBuildEnv(defaultJdk, '17'), + jdkBuildEnv(defaultJdk, '18'), + jdkBuildEnv(defaultJdk, '19'), + ]; + + helper.configure { + file 'job-configuration.yaml' + // We don't require the following, but the build helper plugin apparently does + jdk { + defaultTool "OpenJDK ${defaultJdk} Latest" + } + maven { + defaultTool 'Apache Maven 3.8' + } + } + properties([ + buildDiscarder( + logRotator(daysToKeepStr: '30', numToKeepStr: '10') + ), + // If two builds are about the same branch or pull request, + // the older one will be aborted when the newer one starts. + disableConcurrentBuilds(abortPrevious: true), + helper.generateNotificationProperty() + ]) +} + +// 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 +} + +stage('Build') { + Map executions = [:] + Map> state = [:] + environments.each { BuildEnvironment buildEnv -> + // Don't build environments for newer JDKs when this is a PR + if ( buildEnv.getVersion() != defaultJdk ) { + if ( helper.scmSource.pullRequest ) { + return + } + } + state[buildEnv.tag] = [:] + executions.put(buildEnv.tag, { + runBuildOnNode(buildEnv.node) { + // Use withEnv instead of setting env directly, as that is global! + // See https://github.com/jenkinsci/pipeline-plugin/blob/master/TUTORIAL.md + withEnv(["JAVA_HOME=${tool buildEnv.buildJdkTool}", "PATH+JAVA=${tool buildEnv.buildJdkTool}/bin", "TEST_JAVA_HOME=${tool buildEnv.testJdkTool}"]) { + if ( buildEnv.getVersion() != defaultJdk ) { + state[buildEnv.tag]['additionalOptions'] = " -Ptest.jdk.version=${buildEnv.getTestVersion()} -Porg.gradle.java.installations.paths=${JAVA_HOME},${TEST_JAVA_HOME}"; + } + else { + state[buildEnv.tag]['additionalOptions'] = ""; + } + state[buildEnv.tag]['containerName'] = null; + stage('Checkout') { + checkout scm + } + try { + stage('Start database') { + switch (buildEnv.dbName) { + case "mysql8": + docker.withRegistry('https://index.docker.io/v1/', 'hibernateci.hub.docker.com') { + docker.image('mysql:8.0.21').pull() + } + sh "./docker_db.sh mysql_8_0" + state[buildEnv.tag]['containerName'] = "mysql" + break; + case "mariadb": + docker.withRegistry('https://index.docker.io/v1/', 'hibernateci.hub.docker.com') { + docker.image('mariadb:10.5.8').pull() + } + sh "./docker_db.sh mariadb" + state[buildEnv.tag]['containerName'] = "mariadb" + break; + case "postgresql_9_5": + // use the postgis image to enable the PGSQL GIS (spatial) extension + docker.withRegistry('https://index.docker.io/v1/', 'hibernateci.hub.docker.com') { + docker.image('postgis/postgis:9.5-2.5').pull() + } + sh "./docker_db.sh postgresql_9_5" + state[buildEnv.tag]['containerName'] = "postgres" + break; + case "postgresql_13": + // use the postgis image to enable the PGSQL GIS (spatial) extension + docker.withRegistry('https://index.docker.io/v1/', 'hibernateci.hub.docker.com') { + docker.image('postgis/postgis:13-3.1').pull() + } + sh "./docker_db.sh postgresql_13" + state[buildEnv.tag]['containerName'] = "postgres" + break; + case "oracle": + docker.withRegistry('https://index.docker.io/v1/', 'hibernateci.hub.docker.com') { + docker.image('quillbuilduser/oracle-18-xe').pull() + } + sh "./docker_db.sh oracle_18" + state[buildEnv.tag]['containerName'] = "oracle" + break; + case "db2": + docker.withRegistry('https://index.docker.io/v1/', 'hibernateci.hub.docker.com') { + docker.image('ibmcom/db2:11.5.7.0').pull() + } + sh "./docker_db.sh db2" + state[buildEnv.tag]['containerName'] = "db2" + break; + case "mssql": + docker.image('mcr.microsoft.com/mssql/server:2017-CU13').pull() + sh "./docker_db.sh mssql" + state[buildEnv.tag]['containerName'] = "mssql" + break; + case "sybase": + docker.withRegistry('https://index.docker.io/v1/', 'hibernateci.hub.docker.com') { + docker.image('nguoianphu/docker-sybase').pull() + } + sh "./docker_db.sh sybase" + state[buildEnv.tag]['containerName'] = "sybase" + break; + case "edb": + docker.withRegistry('https://containers.enterprisedb.com', 'hibernateci.containers.enterprisedb.com') { + // withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: 'hibernateci.containers.enterprisedb.com', + // usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD']]) { + // sh 'docker login -u "$USERNAME" -p "$PASSWORD" https://containers.enterprisedb.com' + docker.image('containers.enterprisedb.com/edb/edb-as-lite:v11').pull() + } + sh "./docker_db.sh edb" + state[buildEnv.tag]['containerName'] = "edb" + break; + } + } + stage('Test') { + switch (buildEnv.dbName) { + case "h2": + case "derby": + case "hsqldb": + runTest("-Pdb=${buildEnv.dbName}${state[buildEnv.tag]['additionalOptions']}") + break; + case "mysql8": + runTest("-Pdb=mysql_ci${state[buildEnv.tag]['additionalOptions']}") + break; + case "tidb": + runTest("-Pdb=tidb -DdbHost=localhost:4000${state[buildEnv.tag]['additionalOptions']}", 'TIDB') + break; + case "postgresql_9_5": + case "postgresql_13": + runTest("-Pdb=pgsql_ci${state[buildEnv.tag]['additionalOptions']}") + break; + case "oracle": + runTest("-Pdb=oracle_ci -PexcludeTests=**.LockTest.testQueryTimeout*${state[buildEnv.tag]['additionalOptions']}") + break; + case "hana": + runTest("-Pdb=hana_jenkins${state[buildEnv.tag]['additionalOptions']}", 'HANA') + break; + case "edb": + runTest("-Pdb=edb_ci -DdbHost=localhost:5433${state[buildEnv.tag]['additionalOptions']}") + break; + case "s390x": + runTest("-Pdb=h2${state[buildEnv.tag]['additionalOptions']}") + break; + default: + runTest("-Pdb=${buildEnv.dbName}_ci${state[buildEnv.tag]['additionalOptions']}") + break; + } + } + } + finally { + if ( state[buildEnv.tag]['containerName'] != null ) { + sh "docker rm -f ${state[buildEnv.tag]['containerName']}" + } + // Skip this for PRs + if ( !env.CHANGE_ID && buildEnv.notificationRecipients != null ) { + handleNotifications(currentBuild, buildEnv) + } + } + } + } + }) + } + parallel(executions) +} + +} // End of helper.runWithNotification + +// Job-specific helpers + +BuildEnvironment buildEnv(String version, String dbName) { + return new BuildEnvironment( version, version, dbName, NODE_PATTERN_BASE, null ); +} + +BuildEnvironment buildEnv(String version, String dbName, String node) { + return new BuildEnvironment( version, version, dbName, node, null ); +} + +BuildEnvironment buildEnv(String version, String dbName, String node, String notificationRecipients) { + return new BuildEnvironment( version, version, dbName, node, notificationRecipients ); +} + +BuildEnvironment jdkBuildEnv(String version, String testVersion) { + return new BuildEnvironment( version,testVersion, "h2", NODE_PATTERN_BASE, null ); +} + +BuildEnvironment jdkBuildEnv(String version, String testVersion, String notificationRecipients) { + return new BuildEnvironment( version,testVersion, "h2", NODE_PATTERN_BASE, notificationRecipients ); +} + +public class BuildEnvironment { + private String version; + private String testVersion; + private String buildJdkTool; + private String testJdkTool; + private String dbName; + private String node; + private String notificationRecipients; + + public BuildEnvironment(String version, String testVersion, String dbName, String node, String notificationRecipients) { + this.version = version; + this.testVersion = testVersion; + this.dbName = dbName; + this.node = node; + this.notificationRecipients = notificationRecipients; + this.buildJdkTool = "OpenJDK ${version} Latest"; + this.testJdkTool = "OpenJDK ${testVersion} Latest"; + } + String toString() { getTag() } + String getTag() { "jdk_${testVersion}_${dbName}" } + String getNode() { node } + String getVersion() { version } + String getTestVersion() { testVersion } + String getNotificationRecipients() { notificationRecipients } +} + +void runBuildOnNode(String label, Closure body) { + node( label ) { + pruneDockerContainers() + try { + body() + } + finally { + // If this is a PR, we clean the workspace at the end + if ( env.CHANGE_BRANCH != null ) { + cleanWs() + } + pruneDockerContainers() + } + } +} +void pruneDockerContainers() { + if ( !sh( script: 'command -v docker || true', returnStdout: true ).trim().isEmpty() ) { + sh 'docker container prune -f || true' + sh 'docker image prune -f || true' + sh 'docker network prune -f || true' + sh 'docker volume prune -f || true' + } +} +// Clean by default otherwise the PackagedEntityManager tests fail on a node that previously ran a different DB +void runTest(String goal, String lockableResource = null, boolean clean = true) { + String cmd = "./gradlew" + (clean ? " clean" : "") + " check ${goal} -Plog-test-progress=true --stacktrace"; + try { + if (lockableResource == null) { + timeout( [time: 200, unit: 'MINUTES'] ) { + sh cmd + } + } + else { + lock(lockableResource) { + timeout( [time: 200, unit: 'MINUTES'] ) { + sh cmd + } + } + } + } + finally { + junit '**/target/test-results/test/*.xml,**/target/test-results/testKitTest/*.xml' + } +} + +void handleNotifications(currentBuild, buildEnv) { + def currentResult = getParallelResult(currentBuild, buildEnv.tag) + boolean success = currentResult == 'SUCCESS' || currentResult == 'UNKNOWN' + def previousResult = currentBuild.previousBuild == null ? null : getParallelResult(currentBuild.previousBuild, buildEnv.tag) + + // Ignore success after success + if ( !( success && previousResult == 'SUCCESS' ) ) { + def subject + def body + if ( success ) { + if ( previousResult != 'SUCCESS' && previousResult != null ) { + subject = "${env.JOB_NAME} - Build ${env.BUILD_NUMBER} - Fixed" + body = """

${env.JOB_NAME} - Build ${env.BUILD_NUMBER} - Fixed:

+

Check console output at ${env.BUILD_URL} to view the results.

""" + } + else { + subject = "${env.JOB_NAME} - Build ${env.BUILD_NUMBER} - Success" + body = """

${env.JOB_NAME} - Build ${env.BUILD_NUMBER} - Success:

+

Check console output at ${env.BUILD_URL} to view the results.

""" + } + } + else if ( currentResult == 'FAILURE' ) { + if ( previousResult != null && previousResult == "FAILURE" ) { + subject = "${env.JOB_NAME} - Build ${env.BUILD_NUMBER} - Still failing" + body = """

${env.JOB_NAME} - Build ${env.BUILD_NUMBER} - Still failing:

+

Check console output at ${env.BUILD_URL} to view the results.

""" + } + else { + subject = "${env.JOB_NAME} - Build ${env.BUILD_NUMBER} - Failure" + body = """

${env.JOB_NAME} - Build ${env.BUILD_NUMBER} - Failure:

+

Check console output at ${env.BUILD_URL} to view the results.

""" + } + } + else { + subject = "${env.JOB_NAME} - Build ${env.BUILD_NUMBER} - ${currentResult}" + body = """

${env.JOB_NAME} - Build ${env.BUILD_NUMBER} - ${currentResult}:

+

Check console output at ${env.BUILD_URL} to view the results.

""" + } + + emailext( + subject: subject, + body: body, + to: buildEnv.notificationRecipients + ) + } +} + +@NonCPS +String getParallelResult( RunWrapper build, String parallelBranchName ) { + def visitor = new PipelineNodeGraphVisitor( build.rawBuild ) + def branch = visitor.pipelineNodes.find{ it.type == FlowNodeWrapper.NodeType.PARALLEL && parallelBranchName == it.displayName } + if ( branch == null ) { + echo "Couldn't find parallel branch name '$parallelBranchName'. Available parallel branch names:" + visitor.pipelineNodes.findAll{ it.type == FlowNodeWrapper.NodeType.PARALLEL }.each{ + echo " - ${it.displayName}" + } + return null; + } + return branch.status.result +} \ No newline at end of file diff --git a/README.md b/README.md index dc84c8ec51c1..fabf623322a5 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,7 @@ It also provides an implementation of the JPA specification, which is the standa This is the repository of its source code: see [Hibernate.org](https://hibernate.org/orm/) for additional information. -[![Build Status](https://ci.hibernate.org/job/hibernate-orm-main-h2-main/badge/icon)](https://ci.hibernate.org/job/hibernate-orm-main-h2-main/) -[![Language grade: Java](https://img.shields.io/lgtm/grade/java/g/hibernate/hibernate-orm.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/hibernate/hibernate-orm/context:java) +[![Build Status](https://ci.hibernate.org/job/hibernate-orm-pipeline/job/5.6/badge/icon)](https://ci.hibernate.org/job/hibernate-orm-pipeline/job/5.6/) Building from sources ========= diff --git a/build.gradle b/build.gradle index 15ab92bf1c80..0a85bee8bad8 100644 --- a/build.gradle +++ b/build.gradle @@ -14,7 +14,7 @@ buildscript { classpath 'org.hibernate.build.gradle:hibernate-matrix-testing:3.0.0.Final' classpath 'org.hibernate.build.gradle:version-injection-plugin:1.0.0' classpath 'gradle.plugin.com.github.lburgazzoli:gradle-karaf-plugin:0.5.1' - classpath 'org.asciidoctor:asciidoctor-gradle-plugin:1.5.7' + classpath 'org.asciidoctor:asciidoctor-gradle-jvm:3.3.2' classpath 'de.thetaphi:forbiddenapis:3.0.1' } } @@ -22,7 +22,7 @@ buildscript { plugins { 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 'nu.studer.credentials' version '2.2' 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 diff --git a/changelog.txt b/changelog.txt index 5849eccb6de3..61d9ea5793f6 100644 --- a/changelog.txt +++ b/changelog.txt @@ -3,6 +3,197 @@ Hibernate 5 Changelog Note: Please refer to JIRA to learn more about each issue. +Changes in 5.6.15.Final (February 06, 2023) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/32121 + +** Bug + * [HHH-16049] - Setting a property to its current value with bytecode enhancement enabled results in unnecessary SQL Update in some (many) cases + * [HHH-15665] - Mariadb is missing identifier quote on SEQUENCE QUERY + * [HHH-15618] - Procedure should accept TypedParameterValue as parameter + +** Improvement + * [HHH-15693] - Introduce a fast-path access for ClassLoaderService being retrieved from ServiceRegistry + * [HHH-15690] - HQLQueryPlan to have a direct reference to QueryTranslatorFactory + * [HHH-15685] - Improve efficiency of Dialect lookup in Loader and HqlSqlWalker + +** Patch + * [HHH-15792] - Explicitly add JavaDoc to make @deprecated hint for createSQLQuery visible in Eclipse + + +Changes in 5.6.14.Final (November 04, 2022) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/32120 + +** Improvement + * [HHH-15662] - ClasscastException caused by check for Managed rather than ManagedEntity + + +Changes in 5.6.13.Final (November 03, 2022) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/32112 + +** Bug + * [HHH-15634] - Lazy basic property does not get updated on change + * [HHH-15561] - Function "IDENTITY" not found when inserting audited revision using Hibernate Envers + * [HHH-15554] - Merge of an Entity with an immutable composite user type throws Exception + +** Improvement + * [HHH-15649] - Additional performance fixes relating to Klass's _secondary_super_cache interaction with entity enhancement + * [HHH-15639] - Upgrade to ByteBuddy 1.12.18 + * [HHH-15637] - Upgrade to Byteman 4.0.20 + * [HHH-15616] - Mitigate performance impact of entity enhancement on Klass's _secondary_super_cache + * [HHH-15585] - Add support for DB2 aliases for schema validation + * [HHH-15575] - Make getter org.hibernate.criterion.SimpleExpression#getOp() public + +** Task + * [HHH-15594] - Remove Oracle RDS and all test matrix uses + + +Changes in 5.6.12.Final (September 27, 2022) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/32105 + +** Bug + * [HHH-15523] - Missing use of SqlStringGenerationContext in MapBinder#getFromAndWhereFormula + * [HHH-15522] - Hibernate.isInitialized method not working for Envers Collections + * [HHH-15520] - ValueGeneration on @OneToOne leads to boot error + * [HHH-15505] - Getter of loaded entity returns null when using bytecode enhancement on entity whose field is defined both in mapped superclass and concrete entity + * [HHH-15235] - PropertyAccessException on OneToOne mapping after migration to Hibernate 5.6 + * [HHH-15216] - Cannot change MetadataProvider implementation because JPAXMLOverriddenMetadataProvider is final and precisely expected by a cast operator + * [HHH-15045] - onFlushDirty() invoked on parent entity in a @OneToOne relationship when no table columns are changed + * [HHH-14943] - byNaturalId API creates unparseable query (AND keyword instead of WHERE) + +** Deprecation + * [HHH-15536] - Deprecate SharedSessionContractImplementor#getTransactionStartTimestamp() and CacheTransactionSynchronization#getCurrentTransactionStartTimestamp() + +** New Feature + * [HHH-15508] - Backport Session#getReference(Object) to branch 5.6 + +** Task + * [HHH-15555] - Remove use of @AutomaticFeature in GraalVM module + * [HHH-15538] - Move Jenkinsfile timeout around shell command + + +Changes in 5.6.11.Final (August 30, 2022) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/32089 + +** Bug + * [HHH-15468] - contributor-build.yml has no explicit permissions set + * [HHH-15454] - Primitive type requested from tuple throws exception + * [HHH-15440] - @OneToOne and @OptimisticLock(excluded = true) not working correctly + * [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 + * [HHH-15359] - The entity returned by a merge doesn't contain @ManyToMany relation when the collection resides in @Embeddable + * [HHH-15100] - Limitation of metamodel imports cache causes severe performance drops in large projects + +** Improvement + * [HHH-15466] - Compatibility with Jandex 3.0.0 + +** Task + * [HHH-15451] - Upgrade PostgreSQL JDBC driver to 42.5.0 + * [HHH-15388] - Upgrade to Micrometer 1.9.3 + + +Changes in 5.6.10.Final (July 07, 2022) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/32076 + +** Bug + * [HHH-15281] - INSERTs/UPDATEs no longer executed as JDBC Batch statements if hibernate.temp.use_jdbc_metadata_defaults is set to false + * [HHH-15218] - @OptimisticLocking(DIRTY) leads to wrong query during delete of circular reference + * [HHH-7525] - @Formula annotation with native query returning entity value causes NullPointerException + +** Improvement + * [HHH-15325] - Avoid allocations from BitSet.stream() in AbstractEntityPersister.resolveDirtyAttributeIndexes() + +** Task + * [HHH-15322] - Allow JNDI lookups using the osgi scheme + + +Changes in 5.6.9.Final (May 14, 2022) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/32067 + +** Bug + * [HHH-15270] - Inconsistent precedence of orm.xml implicit catalog over "default_catalog" in XML-mapped entities + * [HHH-15265] - SchemaExport.execute does not add the configured schema to comments + * [HHH-15212] - SchemaExport.execute does not replace the ${schema}-placeholder in HBM database-object with configured schema + * [HHH-15142] - CriteriaQuery with Like predicate fails when repeated with java.lang.IllegalArgumentException: Parameter value [] did not match expected type [java.lang.String (n/a)] + * [HHH-15134] - Update a bytecode enhanced Entity with a Version attribute causes OptimisticLockException + * [HHH-15091] - EntityManager.persist does not verify the existence of the one side of a many-to-one relationship, introduced 5.4.17 + +** Improvement + * [HHH-4384] - @JoinColumn must be set for @AssociationOverride to work + +** Task + * [HHH-15274] - Small optimisation for how LazyAttributeLoadingInterceptor is dealing with lazy fields + * [HHH-15222] - Introduce an helper class SPI for decorating a Session instance when the instance is lazily provided + * [HHH-15178] - Backport Jenkinsfile and GH actions + + +Changes in 5.6.8.Final (April 13, 2022) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/32056 + +** Bug + * [HHH-15147] - hibernate-jpamodelgen-jakarta annotation processor ignores jakarta.* annotations + * [HHH-15141] - Bytecode enhancement fails for a protected, embedded field in a MappedSuperclass from a different package than the entity + * [HHH-15118] - PooledOptimizer generates duplicate ids when several JVMs initialize optimizer and sequence value is the initial value + * [HHH-14487] - PropertyAccessStrategyMapImpl imports wrong class + * [HHH-13694] - Numeric Overflow Exception when retrieving the Meta-data for sequences from Oracle Database + +** Task + * [HHH-15209] - Upgrade to bytebuddy 1.12.9 + * [HHH-15146] - Run tests against hibernate-jpamodelgen-jakarta + + +Changes in 5.6.7.Final (March 16, 2022) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/32053 + +** Improvement + * [HHH-15124] - Relax usage of DeprecationLogger: avoid some confusing reports + * [HHH-15067] - Make NonNullableTransientDependencies.(String propertyName, Object transientEntity) method public + + +Changes in 5.6.6.Final (March 15, 2022) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/32031 + +** Bug + * [HHH-15115] - Deleting an entity with Joined inheritance and default schema set is throwing and error + * [HHH-15113] - Exception setting ParameterExpressions on Update Queries + * [HHH-15105] - Getting the CacheRegionStatistics before executing a query leads to a NPE later on + * [HHH-15097] - Hibernate fails to detect SQL type for AttributeConverter to UUID + * [HHH-15084] - JpaCompliantLifecycleStrategy uses deprecated BeanManager method that's gone in CDI 4.0 + * [HHH-15082] - JDBC Statement leaks after exceptions other than SQLException during insert/update/... + * [HHH-15069] - Backwards-incompatible changes in SequenceStyleGenerator (and others) following default_schema changes + * [HHH-15060] - Fix handling of associations with @NotFound + * [HHH-15051] - Association with id class misses property mapping for target FK attributes + * [HHH-14932] - Spatial support for PostgreSQL 10+ uses invalid WKB dialect + * [HHH-14817] - hibernate-core-jakarta source jar does not contain source + * [HHH-15090] - Access to public field with extended bytecode enhancement returns null for entity lazy-loaded from polymorphic toOne association + +** Improvement + * [HHH-15106] - fk() SQM function + * [HHH-15094] - Handle http://hibernate.org and https://* for all DTDs in LocalXmlResourceResolver + +** Task + * [HHH-15119] - Upgrade to ByteBuddy 1.12.8 + * [HHH-14996] - Upgrade to JBoss Logging Processor (and matching Annotations) 2.2.1.Final + + Changes in 5.6.5.Final (January 25, 2022) ------------------------------------------------------------------------------------------------------------------------ @@ -385,7 +576,7 @@ https://hibernate.atlassian.net/projects/HHH/versions/31844 * [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 * [HHH-14251] - Invalid SQL for @Embedded UPDATE * [HHH-14249] - MultiLineImport fails when script contains blank spaces or tabs at the end of the last sql statement - + * [HHH-14216] - Second-level cache doesn't support @OneToOne Changes in 5.4.14.Final (April 6, 2020) ------------------------------------------------------------------------------------------------------------------------ diff --git a/ci/build.sh b/ci/build.sh index 30176554d921..49f32442aed7 100755 --- a/ci/build.sh +++ b/ci/build.sh @@ -3,9 +3,17 @@ goal= if [ "$RDBMS" == "derby" ]; then goal="-Pdb=derby" +elif [ "$RDBMS" == "hsqldb" ]; then + goal="-Pdb=hsqldb" +elif [ "$RDBMS" == "mysql8" ]; then + goal="-Pdb=mysql_ci" +elif [ "$RDBMS" == "mysql" ]; then + goal="-Pdb=mysql_ci" elif [ "$RDBMS" == "mariadb" ]; then goal="-Pdb=mariadb_ci" -elif [ "$RDBMS" == "postgresql" ]; then +elif [ "$RDBMS" == "postgresql_9_5" ]; then + goal="-Pdb=pgsql_ci" +elif [ "$RDBMS" == "postgresql_13" ]; then goal="-Pdb=pgsql_ci" elif [ "$RDBMS" == "oracle" ]; then # I have no idea why, but these tests don't work on GH Actions @@ -16,6 +24,8 @@ elif [ "$RDBMS" == "mssql" ]; then goal="-Pdb=mssql_ci" elif [ "$RDBMS" == "hana" ]; then goal="-Pdb=hana_ci" +elif [ "$RDBMS" == "sybase" ]; then + goal="-Pdb=sybase_ci" fi exec ./gradlew check ${goal} -Plog-test-progress=true --stacktrace diff --git a/ci/database-start.sh b/ci/database-start.sh index e603a9631bfe..997a450ee9f0 100755 --- a/ci/database-start.sh +++ b/ci/database-start.sh @@ -8,14 +8,18 @@ elif [ "$RDBMS" == 'mysql8' ]; then bash $DIR/../docker_db.sh mysql_8_0 elif [ "$RDBMS" == 'mariadb' ]; then bash $DIR/../docker_db.sh mariadb -elif [ "$RDBMS" == 'postgresql' ]; then +elif [ "$RDBMS" == 'postgresql_9_5' ]; then bash $DIR/../docker_db.sh postgresql_9_5 +elif [ "$RDBMS" == 'postgresql_13' ]; then + bash $DIR/../docker_db.sh postgresql_13 elif [ "$RDBMS" == 'db2' ]; then bash $DIR/../docker_db.sh db2 elif [ "$RDBMS" == 'oracle' ]; then - bash $DIR/../docker_db.sh oracle + bash $DIR/../docker_db.sh oracle_18 elif [ "$RDBMS" == 'mssql' ]; then bash $DIR/../docker_db.sh mssql elif [ "$RDBMS" == 'hana' ]; then bash $DIR/../docker_db.sh hana +elif [ "$RDBMS" == 'sybase' ]; then + bash $DIR/../docker_db.sh sybase fi \ No newline at end of file diff --git a/ci/jpa-2.2-tck.Jenkinsfile b/ci/jpa-2.2-tck.Jenkinsfile index 396536f53c0b..427be4da3fbd 100644 --- a/ci/jpa-2.2-tck.Jenkinsfile +++ b/ci/jpa-2.2-tck.Jenkinsfile @@ -7,6 +7,11 @@ pipeline { tools { jdk 'OpenJDK 8 Latest' } + options { + rateLimitBuilds(throttle: [count: 1, durationName: 'day', userBoost: true]) + buildDiscarder(logRotator(numToKeepStr: '3', artifactNumToKeepStr: '3')) + disableConcurrentBuilds(abortPrevious: true) + } parameters { booleanParam(name: 'NO_SLEEP', defaultValue: true, description: 'Whether the NO_SLEEP patch should be applied to speed up the TCK execution') } diff --git a/ci/jpa-3.0-tck.Jenkinsfile b/ci/jpa-3.0-tck.Jenkinsfile index 3d1aab779c82..150c84175025 100644 --- a/ci/jpa-3.0-tck.Jenkinsfile +++ b/ci/jpa-3.0-tck.Jenkinsfile @@ -7,6 +7,11 @@ pipeline { tools { jdk 'OpenJDK 8 Latest' } + options { + rateLimitBuilds(throttle: [count: 1, durationName: 'day', userBoost: true]) + buildDiscarder(logRotator(numToKeepStr: '3', artifactNumToKeepStr: '3')) + disableConcurrentBuilds(abortPrevious: true) + } parameters { choice(name: 'IMAGE_JDK', choices: ['jdk8', 'jdk11'], description: 'The JDK base image version to use for the TCK image.') string(name: 'TCK_VERSION', defaultValue: '3.0.0', description: 'The version of the Jakarta JPA TCK i.e. `2.2.0` or `3.0.1`') diff --git a/ci/snapshot-publish.Jenkinsfile b/ci/snapshot-publish.Jenkinsfile new file mode 100644 index 000000000000..9883c59457eb --- /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/cockroachdb/matrix.gradle b/databases/cockroachdb/matrix.gradle index c6ed30abc639..797dbd1d257c 100644 --- a/databases/cockroachdb/matrix.gradle +++ b/databases/cockroachdb/matrix.gradle @@ -11,4 +11,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.postgresql:postgresql:42.2.8' \ No newline at end of file +jdbcDependency 'org.postgresql:postgresql:42.5.0' \ No newline at end of file diff --git a/databases/pgsql/matrix.gradle b/databases/pgsql/matrix.gradle index b8ac50d60726..21b9703e577c 100644 --- a/databases/pgsql/matrix.gradle +++ b/databases/pgsql/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.postgresql:postgresql:42.2.19' +jdbcDependency 'org.postgresql:postgresql:42.5.0' diff --git a/docker_db.sh b/docker_db.sh index e88589cada38..6317b5c8755e 100755 --- a/docker_db.sh +++ b/docker_db.sh @@ -1,45 +1,123 @@ #! /bin/bash +if command -v podman > /dev/null; then + CONTAINER_CLI=$(command -v podman) + HEALTCHECK_PATH="{{.State.Healthcheck.Status}}" + # Only use sudo for podman + if command -v sudo > /dev/null; then + PRIVILEGED_CLI="sudo" + else + PRIVILEGED_CLI="" + fi +else + CONTAINER_CLI=$(command -v docker) + HEALTCHECK_PATH="{{.State.Health.Status}}" + PRIVILEGED_CLI="" +fi + mysql_5_7() { - docker rm -f mysql || true - docker run --name mysql -e MYSQL_USER=hibernate_orm_test -e MYSQL_PASSWORD=hibernate_orm_test -e MYSQL_DATABASE=hibernate_orm_test -p3306:3306 -d mysql:5.7 --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci + $CONTAINER_CLI rm -f mysql || true + $CONTAINER_CLI run --name mysql -e MYSQL_USER=hibernate_orm_test -e MYSQL_PASSWORD=hibernate_orm_test -e MYSQL_DATABASE=hibernate_orm_test -e MYSQL_ROOT_PASSWORD=hibernate_orm_test -p3306:3306 -d docker.io/mysql:5.7 --character-set-server=utf8mb4 --collation-server=utf8mb4_bin --skip-character-set-client-handshake --log-bin-trust-function-creators=1 + # Give the container some time to start + OUTPUT= + n=0 + until [ "$n" -ge 5 ] + do + # Need to access STDERR. Thanks for the snippet https://stackoverflow.com/a/56577569/412446 + { OUTPUT="$( { $CONTAINER_CLI logs mysql; } 2>&1 1>&3 3>&- )"; } 3>&1; + if [[ $OUTPUT == *"ready for connections"* ]]; then + break; + fi + n=$((n+1)) + echo "Waiting for MySQL to start..." + sleep 3 + done + if [ "$n" -ge 5 ]; then + echo "MySQL failed to start and configure after 15 seconds" + else + echo "MySQL successfully started" + fi } mysql_8_0() { - docker rm -f mysql || true - docker run --name mysql -e MYSQL_USER=hibernate_orm_test -e MYSQL_PASSWORD=hibernate_orm_test -e MYSQL_ROOT_PASSWORD=hibernate_orm_test -e MYSQL_DATABASE=hibernate_orm_test -p3306:3306 -d mysql:8.0.21 --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci + $CONTAINER_CLI rm -f mysql || true + $CONTAINER_CLI run --name mysql -e MYSQL_USER=hibernate_orm_test -e MYSQL_PASSWORD=hibernate_orm_test -e MYSQL_ROOT_PASSWORD=hibernate_orm_test -e MYSQL_DATABASE=hibernate_orm_test -e MYSQL_ROOT_PASSWORD=hibernate_orm_test -p3306:3306 -d docker.io/mysql:8.0.21 --character-set-server=utf8mb4 --collation-server=utf8mb4_0900_as_cs --skip-character-set-client-handshake --log-bin-trust-function-creators=1 + # Give the container some time to start + OUTPUT= + n=0 + until [ "$n" -ge 5 ] + do + # Need to access STDERR. Thanks for the snippet https://stackoverflow.com/a/56577569/412446 + { OUTPUT="$( { $CONTAINER_CLI logs mysql; } 2>&1 1>&3 3>&- )"; } 3>&1; + if [[ $OUTPUT == *"ready for connections"* ]]; then + break; + fi + n=$((n+1)) + echo "Waiting for MySQL to start..." + sleep 3 + done + if [ "$n" -ge 5 ]; then + echo "MySQL failed to start and configure after 15 seconds" + else + echo "MySQL successfully started" + fi } mariadb() { - docker rm -f mariadb || true - docker run --name mariadb -e MYSQL_USER=hibernate_orm_test -e MYSQL_PASSWORD=hibernate_orm_test -e MYSQL_DATABASE=hibernate_orm_test -e MYSQL_ROOT_PASSWORD=hibernate_orm_test -p3306:3306 -d mariadb:10.5.8 --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci + $CONTAINER_CLI rm -f mariadb || true + $CONTAINER_CLI run --name mariadb -e MYSQL_USER=hibernate_orm_test -e MYSQL_PASSWORD=hibernate_orm_test -e MYSQL_DATABASE=hibernate_orm_test -e MYSQL_ROOT_PASSWORD=hibernate_orm_test -p3306:3306 -d docker.io/mariadb:10.5.8 --character-set-server=utf8mb4 --collation-server=utf8mb4_bin --skip-character-set-client-handshake + OUTPUT= + n=0 + until [ "$n" -ge 5 ] + do + # Need to access STDERR. Thanks for the snippet https://stackoverflow.com/a/56577569/412446 + { OUTPUT="$( { $CONTAINER_CLI logs mariadb; } 2>&1 1>&3 3>&- )"; } 3>&1; + if [[ $OUTPUT == *"ready for connections"* ]]; then + break; + fi + n=$((n+1)) + echo "Waiting for MariaDB to start..." + sleep 3 + done + if [ "$n" -ge 5 ]; then + echo "MariaDB failed to start and configure after 15 seconds" + else + echo "MariaDB successfully started" + fi } postgresql_9_5() { - docker rm -f postgres || true - docker run --name postgres -e POSTGRES_USER=hibernate_orm_test -e POSTGRES_PASSWORD=hibernate_orm_test -e POSTGRES_DB=hibernate_orm_test -p5432:5432 -d postgres:9.5 + $CONTAINER_CLI rm -f postgres || true + $CONTAINER_CLI run --name postgres -e POSTGRES_USER=hibernate_orm_test -e POSTGRES_PASSWORD=hibernate_orm_test -e POSTGRES_DB=hibernate_orm_test -p5432:5432 -d docker.io/postgis/postgis:9.5-2.5 } -postgis(){ - docker rm -f postgis || true - docker run --name postgis -e POSTGRES_USER=hibernate_orm_test -e POSTGRES_PASSWORD=hibernate_orm_test -e POSTGRES_DB=hibernate_orm_test -p5432:5432 -d postgis/postgis:11-2.5 +postgresql_13() { + $CONTAINER_CLI rm -f postgres || true + $CONTAINER_CLI run --name postgres -e POSTGRES_USER=hibernate_orm_test -e POSTGRES_PASSWORD=hibernate_orm_test -e POSTGRES_DB=hibernate_orm_test -p5432:5432 -d docker.io/postgis/postgis:13-3.1 +} + +edb() { + #$CONTAINER_CLI login containers.enterprisedb.com + $CONTAINER_CLI rm -f edb || true + $CONTAINER_CLI run --name edb -e ACCEPT_EULA=Yes -e DATABASE_USER=hibernate_orm_test -e DATABASE_USER_PASSWORD=hibernate_orm_test -e ENTERPRISEDB_PASSWORD=hibernate_orm_test -e DATABASE_NAME=hibernate_orm_test -e PGPORT=5433 -p 5433:5433 --mount type=tmpfs,destination=/edbvolume -d containers.enterprisedb.com/edb/edb-as-lite:v11 } db2() { - docker rm -f db2 || true - docker run --name db2 --privileged -e DB2INSTANCE=orm_test -e DB2INST1_PASSWORD=orm_test -e DBNAME=orm_test -e LICENSE=accept -e AUTOCONFIG=false -e ARCHIVE_LOGS=false -e TO_CREATE_SAMPLEDB=false -e REPODB=false -p 50000:50000 -d ibmcom/db2:11.5.5.0 + echo $CONTAINER_CLI + $PRIVILEGED_CLI $CONTAINER_CLI rm -f db2 || true + $PRIVILEGED_CLI $CONTAINER_CLI run --name db2 --privileged -e DB2INSTANCE=orm_test -e DB2INST1_PASSWORD=orm_test -e DBNAME=orm_test -e LICENSE=accept -e AUTOCONFIG=false -e ARCHIVE_LOGS=false -e TO_CREATE_SAMPLEDB=false -e REPODB=false -p 50000:50000 -d docker.io/ibmcom/db2:11.5.7.0 # Give the container some time to start OUTPUT= while [[ $OUTPUT != *"INSTANCE"* ]]; do echo "Waiting for DB2 to start..." sleep 10 - OUTPUT=$(docker logs db2) + OUTPUT=$($PRIVILEGED_CLI $CONTAINER_CLI logs db2) done - docker exec -t db2 su - orm_test bash -c ". /database/config/orm_test/sqllib/db2profile && /database/config/orm_test/sqllib/bin/db2 'connect to orm_test' && /database/config/orm_test/sqllib/bin/db2 'CREATE USER TEMPORARY TABLESPACE usr_tbsp MANAGED BY AUTOMATIC STORAGE'" + $PRIVILEGED_CLI $CONTAINER_CLI exec -t db2 su - orm_test bash -c ". /database/config/orm_test/sqllib/db2profile && /database/config/orm_test/sqllib/bin/db2 'connect to orm_test' && /database/config/orm_test/sqllib/bin/db2 'CREATE USER TEMPORARY TABLESPACE usr_tbsp MANAGED BY AUTOMATIC STORAGE'" } db2_spatial() { - docker rm -f db2spatial || true + $PRIVILEGED_CLI $CONTAINER_CLI rm -f db2spatial || true temp_dir=$(mktemp -d) cat <${temp_dir}/ewkt.sql create or replace function db2gse.asewkt(geometry db2gse.st_geometry) @@ -78,35 +156,35 @@ CREATE TRANSFORM FOR db2gse.ST_Geometry DB2_PROGRAM ( TO SQL WITH FUNCTION db2gse.geomfromewkt(varchar(32000)) ) ; EOF - docker run --name db2spatial --privileged -e DB2INSTANCE=orm_test -e DB2INST1_PASSWORD=orm_test -e DBNAME=orm_test -e LICENSE=accept -e AUTOCONFIG=false -e ARCHIVE_LOGS=false -e TO_CREATE_SAMPLEDB=false -e REPODB=false \ + $PRIVILEGED_CLI $CONTAINER_CLI run --name db2spatial --privileged -e DB2INSTANCE=orm_test -e DB2INST1_PASSWORD=orm_test -e DBNAME=orm_test -e LICENSE=accept -e AUTOCONFIG=false -e ARCHIVE_LOGS=false -e TO_CREATE_SAMPLEDB=false -e REPODB=false \ -v ${temp_dir}:/conf \ - -p 50000:50000 -d ibmcom/db2:11.5.5.0 + -p 50000:50000 -d docker.io/ibmcom/db2:11.5.5.0 # Give the container some time to start OUTPUT= while [[ $OUTPUT != *"Setup has completed."* ]]; do echo "Waiting for DB2 to start..." sleep 10 - OUTPUT=$(docker logs db2spatial) + OUTPUT=$($PRIVILEGED_CLI $CONTAINER_CLI logs db2spatial) done sleep 10 echo "Enabling spatial extender" - docker exec -t db2spatial su - orm_test bash -c "/database/config/orm_test/sqllib/db2profile && /database/config/orm_test/sqllib/bin/db2se enable_db orm_test" + $PRIVILEGED_CLI $CONTAINER_CLI exec -t db2spatial su - orm_test bash -c "/database/config/orm_test/sqllib/db2profile && /database/config/orm_test/sqllib/bin/db2se enable_db orm_test" echo "Installing required transform group" - docker exec -t db2spatial su - orm_test bash -c "/database/config/orm_test/sqllib/db2profile && /database/config/orm_test/sqllib/bin/db2 'connect to orm_test' && /database/config/orm_test/sqllib/bin/db2 -tvf /conf/ewkt.sql" + $PRIVILEGED_CLI $CONTAINER_CLI exec -t db2spatial su - orm_test bash -c "/database/config/orm_test/sqllib/db2profile && /database/config/orm_test/sqllib/bin/db2 'connect to orm_test' && /database/config/orm_test/sqllib/bin/db2 -tvf /conf/ewkt.sql" } mssql() { - docker rm -f mssql || true - docker run --name mssql -d -p 1433:1433 -e "SA_PASSWORD=Hibernate_orm_test" -e ACCEPT_EULA=Y mcr.microsoft.com/mssql/server:2017-CU13 + $CONTAINER_CLI rm -f mssql || true + $CONTAINER_CLI run --name mssql -d -p 1433:1433 -e "SA_PASSWORD=Hibernate_orm_test" -e ACCEPT_EULA=Y mcr.microsoft.com/mssql/server:2017-CU13 sleep 5 n=0 until [ "$n" -ge 5 ] do # We need a database that uses a non-lock based MVCC approach # https://github.com/microsoft/homebrew-mssql-release/issues/2#issuecomment-682285561 - docker exec mssql bash -c 'echo "create database hibernate_orm_test collate SQL_Latin1_General_CP1_CI_AS; alter database hibernate_orm_test set READ_COMMITTED_SNAPSHOT ON" | /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P Hibernate_orm_test -i /dev/stdin' && break + $CONTAINER_CLI exec mssql bash -c 'echo "create database hibernate_orm_test collate SQL_Latin1_General_CP1_CS_AS; alter database hibernate_orm_test set READ_COMMITTED_SNAPSHOT ON" | /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P Hibernate_orm_test -i /dev/stdin' && break echo "Waiting for SQL Server to start..." n=$((n+1)) sleep 5 @@ -118,19 +196,146 @@ mssql() { fi } -oracle() { - docker rm -f oracle || true - # We need to use the defaults - # SYSTEM/Oracle18 - docker run --shm-size=1536m --name oracle -d -p 1521:1521 --ulimit nofile=1048576:1048576 quillbuilduser/oracle-18-xe - until [ "`docker inspect -f {{.State.Health.Status}} oracle`" == "healthy" ]; +sybase() { + $CONTAINER_CLI rm -f sybase || true + # Yup, that sucks, but on ubuntu we need to use -T11889 as per: https://github.com/DataGrip/docker-env/issues/12 + $CONTAINER_CLI run -d -p 5000:5000 -p 5001:5001 --name sybase --entrypoint /bin/bash docker.io/nguoianphu/docker-sybase -c "source /opt/sybase/SYBASE.sh +/opt/sybase/ASE-16_0/bin/dataserver \ +-d/opt/sybase/data/master.dat \ +-e/opt/sybase/ASE-16_0/install/MYSYBASE.log \ +-c/opt/sybase/ASE-16_0/MYSYBASE.cfg \ +-M/opt/sybase/ASE-16_0 \ +-N/opt/sybase/ASE-16_0/sysam/MYSYBASE.properties \ +-i/opt/sybase \ +-sMYSYBASE \ +-T11889 +RET=\$? +exit 0 +" + + sybase_check() { + $CONTAINER_CLI exec sybase bash -c "source /opt/sybase/SYBASE.sh; +/opt/sybase/OCS-16_0/bin/isql -Usa -P myPassword -S MYSYBASE < 0 +go +quit +EOF +" +} + START_STATUS=0 + j=1 + while (( $j < 30 )); do + echo "Waiting for Sybase to start..." + sleep 1 + j=$((j+1)) + START_STATUS=$(sybase_check | grep '(0 rows affected)' | wc -c) + if (( $START_STATUS > 0 )); then + break + fi + done + if (( $j == 30 )); then + echo "Failed starting Sybase" + $CONTAINER_CLI ps -a + $CONTAINER_CLI logs sybase + sybase_check + exit 1 + fi + + export SYBASE_DB=hibernate_orm_test + export SYBASE_USER=hibernate_orm_test + export SYBASE_PASSWORD=hibernate_orm_test + $CONTAINER_CLI exec sybase bash -c "source /opt/sybase/SYBASE.sh; +cat <<-EOSQL > init1.sql +use master +go +disk resize name='master', size='256m' +go +create database $SYBASE_DB on master = '96m' +go +sp_dboption $SYBASE_DB, \"single user\", true +go +alter database $SYBASE_DB log on master = '50m' +go +use $SYBASE_DB +go +exec sp_extendsegment logsegment, $SYBASE_DB, master +go +use master +go +sp_dboption $SYBASE_DB, \"single user\", false +go +use $SYBASE_DB +go +checkpoint +go +use master +go +create login $SYBASE_USER with password $SYBASE_PASSWORD +go +exec sp_dboption $SYBASE_DB, 'abort tran on log full', true +go +exec sp_dboption $SYBASE_DB, 'allow nulls by default', true +go +exec sp_dboption $SYBASE_DB, 'ddl in tran', true +go +exec sp_dboption $SYBASE_DB, 'trunc log on chkpt', true +go +exec sp_dboption $SYBASE_DB, 'full logging for select into', true +go +exec sp_dboption $SYBASE_DB, 'full logging for alter table', true +go +sp_dboption $SYBASE_DB, \"select into\", true +go +sp_dboption tempdb, 'ddl in tran', true +go +EOSQL + +/opt/sybase/OCS-16_0/bin/isql -Usa -P myPassword -S MYSYBASE -i ./init1.sql + +echo =============== CREATING DB ========================== +cat <<-EOSQL > init2.sql +use $SYBASE_DB +go +sp_adduser '$SYBASE_USER', '$SYBASE_USER', null +go +grant create default to $SYBASE_USER +go +grant create table to $SYBASE_USER +go +grant create view to $SYBASE_USER +go +grant create rule to $SYBASE_USER +go +grant create function to $SYBASE_USER +go +grant create procedure to $SYBASE_USER +go +commit +go +EOSQL + +/opt/sybase/OCS-16_0/bin/isql -Usa -P myPassword -S MYSYBASE -i ./init2.sql" + echo "Sybase successfully started" +} + +oracle_setup() { + HEALTHSTATUS= + until [ "$HEALTHSTATUS" == "healthy" ]; do echo "Waiting for Oracle to start..." - sleep 10; + sleep 5; + # On WSL, health-checks intervals don't work for Podman, so run them manually + if command -v podman > /dev/null; then + $CONTAINER_CLI healthcheck run oracle > /dev/null + fi + HEALTHSTATUS="`$CONTAINER_CLI inspect -f $HEALTCHECK_PATH oracle`" + HEALTHSTATUS=${HEALTHSTATUS##+( )} #Remove longest matching series of spaces from the front + HEALTHSTATUS=${HEALTHSTATUS%%+( )} #Remove longest matching series of spaces from the back done + sleep 2; echo "Oracle successfully started" # We increase file sizes to avoid online resizes as that requires lots of CPU which is restricted in XE - docker exec oracle bash -c "source /home/oracle/.bashrc; bash -c \" + $CONTAINER_CLI exec oracle bash -c "source /home/oracle/.bashrc; bash -c \" cat <$temp_dir/password.json chmod 777 -R $temp_dir - docker rm -f hana || true - docker run -d --name hana -p 39013:39013 -p 39017:39017 -p 39041-39045:39041-39045 -p 1128-1129:1128-1129 -p 59013-59014:59013-59014 \ + $CONTAINER_CLI rm -f hana || true + $CONTAINER_CLI run -d --name hana -p 39013:39013 -p 39017:39017 -p 39041-39045:39041-39045 -p 1128-1129:1128-1129 -p 59013-59014:59013-59014 \ --memory=8g \ --ulimit nofile=1048576:1048576 \ --sysctl kernel.shmmax=1073741824 \ @@ -204,7 +461,7 @@ hana() { --sysctl kernel.shmmni=4096 \ --sysctl kernel.shmall=8388608 \ -v $temp_dir:/config \ - store/saplabs/hanaexpress:2.00.045.00.20200121.1 \ + docker.io/store/saplabs/hanaexpress:2.00.045.00.20200121.1 \ --passwords-url file:///config/password.json \ --agree-to-sap-license # Give the container some time to start @@ -212,22 +469,22 @@ hana() { while [[ $OUTPUT != *"Startup finished"* ]]; do echo "Waiting for HANA to start..." sleep 10 - OUTPUT=$(docker logs hana) + OUTPUT=$($CONTAINER_CLI logs hana) done echo "HANA successfully started" } cockroachdb() { - docker rm -f cockroach || true - docker run -d --name=cockroach -p 26257:26257 -p 8080:8080 cockroachdb/cockroach:v20.2.4 start-single-node --insecure + $CONTAINER_CLI rm -f cockroach || true + $CONTAINER_CLI run -d --name=cockroach -p 26257:26257 -p 8080:8080 docker.io/cockroachdb/cockroach:v20.2.4 start-single-node --insecure OUTPUT= while [[ $OUTPUT != *"CockroachDB node starting"* ]]; do echo "Waiting for CockroachDB to start..." sleep 10 - OUTPUT=$(docker logs cockroach) + OUTPUT=$($CONTAINER_CLI logs cockroach) done echo "Enabling experimental box2d operators" - docker exec -it cockroach bash -c "cat < apply from: rootProject.file( 'gradle/java-module.gradle' ) -apply plugin: 'org.asciidoctor.convert' +apply plugin: 'org.asciidoctor.jvm.convert' apply plugin: 'hibernate-matrix-testing' @@ -180,8 +184,6 @@ task renderTopicalGuides(type: AsciidoctorTask, group: 'Documentation') { description = 'Renders the Topical Guides in HTML format using Asciidoctor.' sourceDir = file( 'src/main/asciidoc/topical' ) outputDir = new File("$buildDir/asciidoc/topical/html_single") - backends "html5" - separateOutputDirs false options logDocuments: true attributes icons: 'font', experimental: true, @@ -205,8 +207,6 @@ task renderGettingStartedGuides(type: AsciidoctorTask, group: 'Documentation') { include 'index.adoc' } outputDir = new File("$buildDir/asciidoc/quickstart/html_single") - backends "html5" - separateOutputDirs false options logDocuments: true attributes icons: 'font', experimental: true, 'source-highlighter': 'prettify' } @@ -216,10 +216,10 @@ task buildTutorialZip(type: Zip) { from 'src/main/asciidoc/quickstart/tutorials' destinationDir = tasks.renderGettingStartedGuides.outputDir archiveName = 'hibernate-tutorials.zip' - expand( + expand( version: project.version, slf4j: "1.7.5", - junit: project.junitVersion, + junit: project.junit4Version, h2: project.h2Version ) } @@ -235,8 +235,6 @@ task renderUserGuide(type: AsciidoctorTask, group: 'Documentation') { include 'Hibernate_User_Guide.adoc' } outputDir = new File("$buildDir/asciidoc/userguide/html_single") - backends "html5" - separateOutputDirs false options logDocuments: true attributes icons: 'font', experimental: true, 'source-highlighter': 'prettify', @@ -272,8 +270,6 @@ task renderIntegrationGuide(type: AsciidoctorTask, group: 'Documentation') { include 'Hibernate_Integration_Guide.adoc' } outputDir = new File("$buildDir/asciidoc/integrationguide/html_single") - backends "html5" - separateOutputDirs false options logDocuments: true attributes icons: 'font', experimental: true, @@ -320,3 +316,10 @@ buildDocsForPublishing.dependsOn renderIntegrationGuide checkstyleMain.exclude '**/org/hibernate/userguide/model/*' +tasks.withType(AsciidoctorTask).configureEach { + baseDirFollowsSourceDir() + outputOptions { + separateOutputDirs = false + backends 'html5' + } +} diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/associations.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/associations.adoc index 1e12b48ce4dd..a70735be9f3f 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/associations.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/associations.adoc @@ -417,21 +417,39 @@ 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 Foreign Key, -it's possible to bump into inconsistencies if the child record cannot reference a parent entity. +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. -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. +[WARNING] +==== +Not enforcing physical foreign-keys at the database level is highly discouraged. +==== -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`. +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 - -[NOTE] +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] ==== -The `@ManyToOne` and `@OneToOne` associations that are annotated with `@NotFound(action = NotFoundAction.IGNORE)` are always fetched eagerly even if the `fetch` strategy is set to `FetchType.LAZY`. +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`. ==== + Considering the following `City` and `Person` entity mappings: [[associations-not-found-domain-model-example]] @@ -457,29 +475,29 @@ 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's name: +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] ---- @@ -487,6 +505,62 @@ include::{sourcedir}/NotFoundTest.java[tags=associations-not-found-non-existing- ---- ==== +`@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] +---- +==== + + [[associations-any]] ==== `@Any` mapping 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 004c51a5c63b..e903fa2b100d 100644 --- a/documentation/src/test/java/org/hibernate/userguide/associations/NotFoundTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/associations/NotFoundTest.java @@ -1,21 +1,35 @@ package org.hibernate.userguide.associations; import java.io.Serializable; +import java.util.List; +import java.util.Map; + 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.Table; +import org.hibernate.Criteria; +import org.hibernate.Session; import org.hibernate.annotations.NotFound; import org.hibernate.annotations.NotFoundAction; +import org.hibernate.criterion.ForeignKeyExpression; +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.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.testing.jdbc.SQLStatementInterceptor; +import org.junit.After; +import org.junit.Before; import org.junit.Test; +import static org.assertj.core.api.Assertions.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.assertNull; @@ -24,6 +38,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 +53,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 ).isEmpty(); + + final List nonNullResults = entityManager + .createQuery( "from Person p where p.city.id is not null", Person.class ) + .list(); + assertThat( nonNullResults ).isEmpty(); + //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 ).isEmpty(); + + final List nonNullResults = entityManager + .createQuery( "select p.name from Person p where fk( p.city ) is not null", String.class ) + .list(); + assertThat( nonNullResults ).hasSize( 1 ); + assertThat( nonNullResults.get( 0 ) ).isEqualTo( "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.getQueryCount() ).isEqualTo( 2 ); + assertThat( sqlStatementInterceptor.getSqlQueries().get( 0 ) ).doesNotContain( " join " ); + assertThat( sqlStatementInterceptor.getSqlQueries().get( 1 ) ).doesNotContain( " 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 ).isEmpty(); + + 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 ).hasSize( 1 ); + assertThat( nonNullResults.get( 0 ) ).isEqualTo( "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 ).hasSize( 1 ); + assertThat( foreigKeyResults.get( 0 ) ).isEqualTo( 1 ); + //end::associations-not-found-fk-criteria-example[] + + // In addition, make sure that the two executed queries do not create a join + assertThat( sqlStatementInterceptor.getQueryCount() ).isEqualTo( 3 ); + assertThat( sqlStatementInterceptor.getSqlQueries().get( 0 ) ).doesNotContain( " join " ); + assertThat( sqlStatementInterceptor.getSqlQueries().get( 1 ) ).doesNotContain( " join " ); + assertThat( sqlStatementInterceptor.getSqlQueries().get( 2 ) ).doesNotContain( " 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 - @NotFound ( action = NotFoundAction.IGNORE ) - @JoinColumn( - name = "cityName", - referencedColumnName = "name", - insertable = false, - updatable = false - ) + @NotFound(action = NotFoundAction.IGNORE) + @JoinColumn(name = "city_fk", referencedColumnName = "id") 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 +240,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 +283,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/caching/SecondLevelCacheTest.java b/documentation/src/test/java/org/hibernate/userguide/caching/SecondLevelCacheTest.java index a5602e7ff903..f545f0d7ebe6 100644 --- a/documentation/src/test/java/org/hibernate/userguide/caching/SecondLevelCacheTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/caching/SecondLevelCacheTest.java @@ -22,23 +22,27 @@ import org.hibernate.Session; import org.hibernate.annotations.CacheConcurrencyStrategy; import org.hibernate.annotations.NaturalId; +import org.hibernate.annotations.NaturalIdCache; import org.hibernate.cfg.AvailableSettings; +import org.hibernate.internal.SessionFactoryImpl; import org.hibernate.jpa.QueryHints; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.stat.CacheRegionStatistics; import org.hibernate.stat.Statistics; +import org.hibernate.testing.TestForIssue; import org.junit.Ignore; import org.junit.Test; import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; /** * @author Vlad Mihalcea */ -@Ignore + //@FailureExpected( jiraKey = "HHH-12146", message = "No idea why those changes cause this to fail, especially in the way it does" ) public class SecondLevelCacheTest extends BaseEntityManagerFunctionalTestCase { @@ -251,10 +255,89 @@ public void testCache() { }); } + @Test + @TestForIssue( jiraKey = "HHH-14944") // issue is also reproduceable in Hibernate 5.4 + public void testCacheVerifyHits() { + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.persist( new Person() ); + Person aPerson= new Person(); + aPerson.setName( "John Doe" ); + aPerson.setCode( "unique-code" ); + entityManager.persist( aPerson ); + Session session = entityManager.unwrap(Session.class); + SessionFactoryImpl sfi = (SessionFactoryImpl) session.getSessionFactory(); + sfi.getStatistics().clear(); + return aPerson; + }); + + doInJPA(this::entityManagerFactory, entityManager -> { + log.info("Native load by natural-id, generate first hit"); + + Session session = entityManager.unwrap(Session.class); + SessionFactoryImpl sfi = (SessionFactoryImpl) session.getSessionFactory(); + //tag::caching-entity-natural-id-example[] + Person person = session + .byNaturalId(Person.class) + .using("code", "unique-code") + .load(); + + assertNotNull(person); + log.info("NaturalIdCacheHitCount: " + sfi.getStatistics().getNaturalIdCacheHitCount()); + log.info("SecondLevelCacheHitCount: " + sfi.getStatistics().getSecondLevelCacheHitCount()); + assertEquals(1, sfi.getStatistics().getNaturalIdCacheHitCount()); + assertEquals(1, sfi.getStatistics().getSecondLevelCacheHitCount()); + //end::caching-entity-natural-id-example[] + }); + + doInJPA(this::entityManagerFactory, entityManager -> { + log.info("Native load by natural-id, generate second hit"); + + Session session = entityManager.unwrap(Session.class); + SessionFactoryImpl sfi = (SessionFactoryImpl) session.getSessionFactory(); + //tag::caching-entity-natural-id-example[] + Person person = session.bySimpleNaturalId(Person.class).load("unique-code"); + assertNotNull(person); + + // resolve in persistence context (first level cache) + session.bySimpleNaturalId(Person.class).load("unique-code"); + log.info("NaturalIdCacheHitCount: " + sfi.getStatistics().getNaturalIdCacheHitCount()); + log.info("SecondLevelCacheHitCount: " + sfi.getStatistics().getSecondLevelCacheHitCount()); + assertEquals(2, sfi.getStatistics().getNaturalIdCacheHitCount()); + assertEquals(2, sfi.getStatistics().getSecondLevelCacheHitCount()); + + session.clear(); + // persistence context (first level cache) empty, should resolve from second level cache + log.info("Native load by natural-id, generate third hit"); + person = session.bySimpleNaturalId(Person.class).load("unique-code"); + log.info("NaturalIdCacheHitCount: " + sfi.getStatistics().getNaturalIdCacheHitCount()); + log.info("SecondLevelCacheHitCount: " + sfi.getStatistics().getSecondLevelCacheHitCount()); + assertNotNull(person); + assertEquals(3, sfi.getStatistics().getNaturalIdCacheHitCount()); + assertEquals(3, sfi.getStatistics().getSecondLevelCacheHitCount()); + + //Remove the entity from the persistence context + Long id = person.getId(); + + entityManager.detach(person); // still it should resolve from second level cache after this + + log.info("Native load by natural-id, generate 4. hit"); + person = session.bySimpleNaturalId(Person.class).load("unique-code"); + log.info("NaturalIdCacheHitCount: " + sfi.getStatistics().getNaturalIdCacheHitCount()); + assertEquals("we expected now 4 hits" , 4, sfi.getStatistics().getNaturalIdCacheHitCount()); + assertNotNull(person); + session.delete(person); // evicts natural-id from first & second level cache + person = session.bySimpleNaturalId(Person.class).load("unique-code"); + assertEquals(4, sfi.getStatistics().getNaturalIdCacheHitCount()); // thus hits should not increment + + //end::caching-entity-natural-id-example[] + }); + } + //tag::caching-entity-natural-id-mapping-example[] @Entity(name = "Person") @Cacheable @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE) + @NaturalIdCache public static class Person { @Id diff --git a/documentation/src/test/java/org/hibernate/userguide/envers/EntityTypeChangeAuditTest.java b/documentation/src/test/java/org/hibernate/userguide/envers/EntityTypeChangeAuditTest.java index b6fc6d3bdc5e..65d7480b6fb7 100644 --- a/documentation/src/test/java/org/hibernate/userguide/envers/EntityTypeChangeAuditTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/envers/EntityTypeChangeAuditTest.java @@ -9,7 +9,6 @@ import java.util.Arrays; import java.util.Date; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Set; import javax.persistence.Column; diff --git a/documentation/src/test/java/org/hibernate/userguide/hql/HQLTest.java b/documentation/src/test/java/org/hibernate/userguide/hql/HQLTest.java index 98070dc5f67a..95027b62eb96 100644 --- a/documentation/src/test/java/org/hibernate/userguide/hql/HQLTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/hql/HQLTest.java @@ -31,6 +31,7 @@ import org.hibernate.dialect.Oracle8iDialect; import org.hibernate.dialect.PostgreSQL81Dialect; import org.hibernate.dialect.SQLServerDialect; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.type.StringType; import org.hibernate.userguide.model.AddressType; @@ -1314,6 +1315,7 @@ public void test_hql_sqrt_function_example() { @Test @SkipForDialect(SQLServerDialect.class) + @SkipForDialect(value = SybaseASE15Dialect.class, comment = "current_date requires parenthesis which we don't render") @SkipForDialect(value = DerbyDialect.class, comment = "Comparisons between 'DATE' and 'TIMESTAMP' are not supported") public void test_hql_current_date_function_example() { doInJPA( this::entityManagerFactory, entityManager -> { @@ -1356,6 +1358,7 @@ public void test_hql_current_time_function_example() { } @Test + @SkipForDialect(value = SybaseASE15Dialect.class, comment = "current_timestamp requires parenthesis which we don't render") public void test_hql_current_timestamp_function_example() { doInJPA( this::entityManagerFactory, entityManager -> { //tag::hql-current-timestamp-function-example[] @@ -1428,6 +1431,7 @@ public void test_hql_year_function_example() { @Test @SkipForDialect(SQLServerDialect.class) + @SkipForDialect(value = SybaseASE15Dialect.class, comment = "No proper implementation for the STR function available") public void test_hql_str_function_example() { doInJPA( this::entityManagerFactory, entityManager -> { //tag::hql-str-function-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 6a7a7ffb52f7..91c7d25e3597 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 @@ -7,6 +7,7 @@ package org.hibernate.userguide.mapping.basic; import java.util.BitSet; +import javax.persistence.Column; import javax.persistence.ColumnResult; import javax.persistence.ConstructorResult; import javax.persistence.Entity; @@ -94,7 +95,7 @@ protected boolean isCleanupTestDataRequired() { query = "SELECT " + " pr.id AS \"pr.id\", " + - " pr.bitset AS \"pr.bitset\" " + + " pr.bitset_col AS \"pr.bitset\" " + "FROM Product pr " + "WHERE pr.id = :id", resultSetMapping = "Person" @@ -117,6 +118,7 @@ public static class Product { private Integer id; @Type( type = "bitset" ) + @Column(name = "bitset_col") private BitSet bitSet; //Constructors, getters, and setters are omitted for brevity diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/ClobCharArrayTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/ClobCharArrayTest.java index feb6a1591ceb..c56769569d2b 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/ClobCharArrayTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/ClobCharArrayTest.java @@ -10,8 +10,10 @@ import javax.persistence.Id; import javax.persistence.Lob; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.testing.SkipForDialect; import org.junit.Test; import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; @@ -20,6 +22,7 @@ /** * @author Vlad Mihalcea */ +@SkipForDialect(value = SybaseASE15Dialect.class, comment = "jTDS driver doesn't implement character stream handling") public class ClobCharArrayTest extends BaseEntityManagerFunctionalTestCase { @Override diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/ClobStringTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/ClobStringTest.java index 5df1221983db..0a216a292ce9 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/ClobStringTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/ClobStringTest.java @@ -10,8 +10,10 @@ import javax.persistence.Id; import javax.persistence.Lob; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.testing.SkipForDialect; import org.junit.Test; import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; @@ -20,6 +22,7 @@ /** * @author Vlad Mihalcea */ +@SkipForDialect(value = SybaseASE15Dialect.class, comment = "jTDS driver doesn't implement character stream handling") public class ClobStringTest extends BaseEntityManagerFunctionalTestCase { @Override diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/ClobTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/ClobTest.java index 503413c63659..f7edd1f10998 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/ClobTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/ClobTest.java @@ -16,9 +16,11 @@ import javax.persistence.Lob; import org.hibernate.Session; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.engine.jdbc.ClobProxy; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.testing.SkipForDialect; import org.junit.Test; import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; @@ -28,6 +30,7 @@ /** * @author Vlad Mihalcea */ +@SkipForDialect(value = SybaseASE15Dialect.class, comment = "jTDS driver doesn't implement character stream handling") public class ClobTest extends BaseEntityManagerFunctionalTestCase { @Override diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/NClobCharArrayTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/NClobCharArrayTest.java index 50fa6a64d2d1..f2caf437df51 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/NClobCharArrayTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/NClobCharArrayTest.java @@ -14,6 +14,7 @@ import org.hibernate.dialect.DB2Dialect; import org.hibernate.dialect.MySQL5Dialect; import org.hibernate.dialect.PostgreSQL81Dialect; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.testing.SkipForDialect; @@ -33,6 +34,7 @@ }, comment = "@see https://hibernate.atlassian.net/browse/HHH-10693 and https://hibernate.atlassian.net/browse/HHH-10695 and https://hibernate.atlassian.net/browse/HHH-10473" ) +@SkipForDialect(value = SybaseASE15Dialect.class, comment = "jTDS driver doesn't implement nationalized handling") public class NClobCharArrayTest extends BaseEntityManagerFunctionalTestCase { @Override diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/NClobStringTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/NClobStringTest.java index 03d20f9234bb..15c7b5e63120 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/NClobStringTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/NClobStringTest.java @@ -14,6 +14,7 @@ import org.hibernate.dialect.DB2Dialect; import org.hibernate.dialect.MySQL5Dialect; import org.hibernate.dialect.PostgreSQL81Dialect; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.testing.SkipForDialect; @@ -33,6 +34,7 @@ }, comment = "@see https://hibernate.atlassian.net/browse/HHH-10693 and https://hibernate.atlassian.net/browse/HHH-10695 and https://hibernate.atlassian.net/browse/HHH-10473" ) +@SkipForDialect(value = SybaseASE15Dialect.class, comment = "jTDS driver doesn't implement nationalized handling") public class NClobStringTest extends BaseEntityManagerFunctionalTestCase { @Override diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/NClobTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/NClobTest.java index 8061589e4253..01eaae8b2035 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/NClobTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/NClobTest.java @@ -22,6 +22,7 @@ import org.hibernate.dialect.DB2Dialect; import org.hibernate.dialect.MySQL5Dialect; import org.hibernate.dialect.PostgreSQL81Dialect; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.engine.jdbc.NClobProxy; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; @@ -45,6 +46,7 @@ }, comment = "@see https://hibernate.atlassian.net/browse/HHH-10693 and https://hibernate.atlassian.net/browse/HHH-10695 and https://hibernate.atlassian.net/browse/HHH-10473" ) +@SkipForDialect(value = SybaseASE15Dialect.class, comment = "jTDS driver doesn't implement nationalized handling") public class NClobTest extends BaseEntityManagerFunctionalTestCase { @Override diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/NationalizedTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/NationalizedTest.java index 22ba662aa263..c3257a988c1a 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/NationalizedTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/NationalizedTest.java @@ -12,6 +12,7 @@ import org.hibernate.annotations.Nationalized; import org.hibernate.dialect.DerbyDialect; import org.hibernate.dialect.PostgreSQL81Dialect; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.testing.SkipForDialect; @@ -30,6 +31,7 @@ }, comment = "@see https://hibernate.atlassian.net/browse/HHH-10693 and Derby doesn't support nationalized type" ) +@SkipForDialect(value = SybaseASE15Dialect.class, comment = "jTDS driver doesn't implement nationalized handling") public class NationalizedTest extends BaseEntityManagerFunctionalTestCase { @Override diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/SubselectTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/SubselectTest.java index d64f14813fe6..438f72f7b954 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/SubselectTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/SubselectTest.java @@ -16,6 +16,7 @@ import org.hibernate.annotations.Subselect; import org.hibernate.annotations.Synchronize; import org.hibernate.dialect.DerbyDialect; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.testing.SkipForDialect; @@ -28,6 +29,7 @@ * @author Vlad Mihalcea */ @SkipForDialect(value = DerbyDialect.class, comment = "Derby doesn't support a CONCAT function") +@SkipForDialect(value = SybaseASE15Dialect.class, comment = "Sybase doesn't support a CONCAT function") public class SubselectTest extends BaseEntityManagerFunctionalTestCase { @Override 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 870997a9b4d3..fec3260c73f6 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 @@ -15,11 +15,13 @@ import javax.persistence.Id; import org.hibernate.annotations.ValueGenerationType; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.tuple.AnnotationValueGeneration; import org.hibernate.tuple.GenerationTiming; import org.hibernate.tuple.ValueGenerator; +import org.hibernate.testing.SkipForDialect; import org.junit.Test; import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; @@ -27,6 +29,7 @@ /** * @author Vlad Mihalcea */ +@SkipForDialect(value = SybaseASE15Dialect.class, comment = "current_timestamp requires parenthesis which we don't render") public class DatabaseValueGenerationTest extends BaseEntityManagerFunctionalTestCase { @Override 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 be78241ad8ed..5afa0bd0b976 100644 --- a/documentation/src/test/java/org/hibernate/userguide/pc/CascadeOnDeleteTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/pc/CascadeOnDeleteTest.java @@ -10,6 +10,8 @@ import org.hibernate.annotations.OnDeleteAction; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; import org.junit.Test; import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; @@ -17,6 +19,7 @@ /** * @author Vlad Mihalcea */ +@RequiresDialectFeature(DialectChecks.SupportsCascadeDeleteCheck.class) public class CascadeOnDeleteTest extends BaseEntityManagerFunctionalTestCase { @Override diff --git a/documentation/src/test/java/org/hibernate/userguide/schema/UniqueConstraintTest.java b/documentation/src/test/java/org/hibernate/userguide/schema/UniqueConstraintTest.java index ba6f042ff885..6651ccd70f30 100644 --- a/documentation/src/test/java/org/hibernate/userguide/schema/UniqueConstraintTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/schema/UniqueConstraintTest.java @@ -19,9 +19,11 @@ import org.hibernate.annotations.ColumnDefault; import org.hibernate.annotations.DynamicInsert; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.exception.ConstraintViolationException; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.util.ExceptionUtil; import org.junit.Test; @@ -44,6 +46,7 @@ protected Class[] getAnnotatedClasses() { } @Test + @SkipForDialect(value = SybaseASE15Dialect.class, comment = "Missing constraint violation extractor") public void test() { //tag::schema-generation-columns-unique-constraint-persist-example[] Author _author = doInJPA( this::entityManagerFactory, entityManager -> { diff --git a/documentation/src/test/resources/hibernate.properties b/documentation/src/test/resources/hibernate.properties index 8b93710f2628..968847a8f3f7 100644 --- a/documentation/src/test/resources/hibernate.properties +++ b/documentation/src/test/resources/hibernate.properties @@ -10,6 +10,7 @@ hibernate.connection.driver_class @jdbc.driver@ hibernate.connection.url @jdbc.url@ hibernate.connection.username @jdbc.user@ hibernate.connection.password @jdbc.pass@ +hibernate.connection.init_sql @connection.init_sql@ hibernate.connection.pool_size 5 diff --git a/gradle/databases.gradle b/gradle/databases.gradle index cf54fda5dd4d..bd2b65b16043 100644 --- a/gradle/databases.gradle +++ b/gradle/databases.gradle @@ -16,20 +16,23 @@ ext { 'jdbc.user' : 'sa', 'jdbc.pass' : '', 'jdbc.url' : 'jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1;LOCK_TIMEOUT=10000', + 'connection.init_sql' : '' ], hsqldb : [ 'db.dialect' : 'org.hibernate.dialect.HSQLDialect', 'jdbc.driver': 'org.hsqldb.jdbc.JDBCDriver', 'jdbc.user' : 'sa', 'jdbc.pass' : '', - 'jdbc.url' : 'jdbc:hsqldb:mem:test' + 'jdbc.url' : 'jdbc:hsqldb:mem:test', + 'connection.init_sql' : '' ], derby : [ 'db.dialect' : 'org.hibernate.dialect.DerbyTenSevenDialect', 'jdbc.driver': 'org.apache.derby.jdbc.EmbeddedDriver', 'jdbc.user' : 'hibernate_orm_test', 'jdbc.pass' : 'hibernate_orm_test', - 'jdbc.url' : 'jdbc:derby:target/tmp/derby/hibernate_orm_test;databaseName=hibernate_orm_test;create=true' + 'jdbc.url' : 'jdbc:derby:target/tmp/derby/hibernate_orm_test;databaseName=hibernate_orm_test;create=true', + 'connection.init_sql' : '' ], pgsql : [ 'db.dialect' : 'org.hibernate.dialect.PostgreSQL95Dialect', @@ -37,7 +40,8 @@ ext { 'jdbc.user' : 'hibernate_orm_test', 'jdbc.pass' : 'hibernate_orm_test', // Disable prepared statement caching due to https://www.postgresql.org/message-id/CAEcMXhmmRd4-%2BNQbnjDT26XNdUoXdmntV9zdr8%3DTu8PL9aVCYg%40mail.gmail.com - 'jdbc.url' : 'jdbc:postgresql://' + dbHost + '/hibernate_orm_test?preparedStatementCacheQueries=0' + 'jdbc.url' : 'jdbc:postgresql://' + dbHost + '/hibernate_orm_test?preparedStatementCacheQueries=0', + 'connection.init_sql' : '' ], pgsql_docker : [ 'db.dialect' : 'org.hibernate.dialect.PostgreSQL10Dialect', @@ -45,7 +49,8 @@ ext { 'jdbc.user' : 'hibernate_orm_test', 'jdbc.pass' : 'hibernate_orm_test', // Disable prepared statement caching due to https://www.postgresql.org/message-id/CAEcMXhmmRd4-%2BNQbnjDT26XNdUoXdmntV9zdr8%3DTu8PL9aVCYg%40mail.gmail.com - 'jdbc.url' : 'jdbc:postgresql://' + dbHost + '/hibernate_orm_test?preparedStatementCacheQueries=0' + 'jdbc.url' : 'jdbc:postgresql://' + dbHost + '/hibernate_orm_test?preparedStatementCacheQueries=0', + 'connection.init_sql' : '' ], pgsql_ci : [ 'db.dialect' : 'org.hibernate.dialect.PostgreSQL95Dialect', @@ -53,21 +58,41 @@ ext { 'jdbc.user' : 'hibernate_orm_test', 'jdbc.pass' : 'hibernate_orm_test', // Disable prepared statement caching due to https://www.postgresql.org/message-id/CAEcMXhmmRd4-%2BNQbnjDT26XNdUoXdmntV9zdr8%3DTu8PL9aVCYg%40mail.gmail.com - 'jdbc.url' : 'jdbc:postgresql://' + dbHost + '/hibernate_orm_test?preparedStatementCacheQueries=0' + 'jdbc.url' : 'jdbc:postgresql://' + dbHost + '/hibernate_orm_test?preparedStatementCacheQueries=0', + 'connection.init_sql' : '' + ], + sybase_ci : [ + 'db.dialect' : 'org.hibernate.dialect.SybaseASE157Dialect', + 'jdbc.driver': 'net.sourceforge.jtds.jdbc.Driver', + 'jdbc.user' : 'hibernate_orm_test', + 'jdbc.pass' : 'hibernate_orm_test', + // Disable prepared statement caching to avoid issues with changing schemas + 'jdbc.url' : 'jdbc:jtds:sybase://' + dbHost + ':5000/hibernate_orm_test;maxStatements=0;cacheMetaData=false', + 'connection.init_sql' : 'set ansinull on' ], mysql : [ 'db.dialect' : 'org.hibernate.dialect.MySQL57Dialect', 'jdbc.driver': 'com.mysql.jdbc.Driver', 'jdbc.user' : 'hibernateormtest', 'jdbc.pass' : 'hibernateormtest', - 'jdbc.url' : 'jdbc:mysql://' + dbHost + '/hibernate_orm_test' + 'jdbc.url' : 'jdbc:mysql://' + dbHost + '/hibernate_orm_test', + 'connection.init_sql' : '' ], mysql_docker : [ 'db.dialect' : 'org.hibernate.dialect.MySQL57Dialect', 'jdbc.driver': 'com.mysql.jdbc.Driver', 'jdbc.user' : 'hibernate_orm_test', 'jdbc.pass' : 'hibernate_orm_test', - 'jdbc.url' : 'jdbc:mysql://' + dbHost + '/hibernate_orm_test?useSSL=false' + 'jdbc.url' : 'jdbc:mysql://' + dbHost + '/hibernate_orm_test?useSSL=false', + 'connection.init_sql' : '' + ], + mysql_ci : [ + 'db.dialect' : 'org.hibernate.dialect.MySQL8Dialect', + 'jdbc.driver': 'com.mysql.jdbc.Driver', + 'jdbc.user' : 'hibernate_orm_test', + 'jdbc.pass' : 'hibernate_orm_test', + 'jdbc.url' : 'jdbc:mysql://' + dbHost + '/hibernate_orm_test?allowPublicKeyRetrieval=true', + 'connection.init_sql' : '' ], // uses docker mysql_8_0 mysql8_spatial_ci: [ @@ -75,28 +100,32 @@ ext { 'jdbc.driver': 'com.mysql.cj.jdbc.Driver', 'jdbc.user' : 'hibernate_orm_test', 'jdbc.pass' : 'hibernate_orm_test', - 'jdbc.url' : 'jdbc:mysql://' + dbHost + '/hibernate_orm_test?allowPublicKeyRetrieval=true&useSSL=false' + 'jdbc.url' : 'jdbc:mysql://' + dbHost + '/hibernate_orm_test?allowPublicKeyRetrieval=true&useSSL=false', + 'connection.init_sql' : '' ], mariadb : [ 'db.dialect' : 'org.hibernate.dialect.MariaDB103Dialect', 'jdbc.driver': 'org.mariadb.jdbc.Driver', 'jdbc.user' : 'hibernate_orm_test', 'jdbc.pass' : 'hibernate_orm_test', - 'jdbc.url' : 'jdbc:mariadb://' + dbHost + '/hibernate_orm_test' + 'jdbc.url' : 'jdbc:mariadb://' + dbHost + '/hibernate_orm_test', + 'connection.init_sql' : '' ], mariadb_ci : [ 'db.dialect' : 'org.hibernate.dialect.MariaDB103Dialect', 'jdbc.driver': 'org.mariadb.jdbc.Driver', 'jdbc.user' : 'root', 'jdbc.pass' : 'hibernate_orm_test', - 'jdbc.url' : 'jdbc:mariadb://' + dbHost + '/hibernate_orm_test' + 'jdbc.url' : 'jdbc:mariadb://' + dbHost + '/hibernate_orm_test', + 'connection.init_sql' : '' ], mariadb_spatial_ci : [ 'db.dialect' : 'org.hibernate.spatial.dialect.mariadb.MariaDB103SpatialDialect', 'jdbc.driver': 'org.mariadb.jdbc.Driver', 'jdbc.user' : 'root', 'jdbc.pass' : 'hibernate_orm_test', - 'jdbc.url' : 'jdbc:mariadb://' + dbHost + '/hibernate_orm_test' + 'jdbc.url' : 'jdbc:mariadb://' + dbHost + '/hibernate_orm_test', + 'connection.init_sql' : '' ], postgis : [ 'db.dialect' : 'org.hibernate.spatial.dialect.postgis.PostgisPG95Dialect', @@ -104,14 +133,24 @@ ext { 'jdbc.user' : 'hibernate_orm_test', 'jdbc.pass' : 'hibernate_orm_test', // Disable prepared statement caching due to https://www.postgresql.org/message-id/CAEcMXhmmRd4-%2BNQbnjDT26XNdUoXdmntV9zdr8%3DTu8PL9aVCYg%40mail.gmail.com - 'jdbc.url' : 'jdbc:postgresql://' + dbHost + '/hibernate_orm_test?preparedStatementCacheQueries=0' + 'jdbc.url' : 'jdbc:postgresql://' + dbHost + '/hibernate_orm_test?preparedStatementCacheQueries=0', + 'connection.init_sql' : '' ], oracle : [ 'db.dialect' : 'org.hibernate.dialect.Oracle10gDialect', 'jdbc.driver': 'oracle.jdbc.OracleDriver', 'jdbc.user' : 'hibernate_orm_test', 'jdbc.pass' : 'hibernate_orm_test', - 'jdbc.url' : 'jdbc:oracle:thin:@' + dbHost + ':1521/xe' + 'jdbc.url' : 'jdbc:oracle:thin:@' + dbHost + ':1521/xe', + 'connection.init_sql' : '' + ], + oracle_jenkins : [ + 'db.dialect' : 'org.hibernate.dialect.Oracle12cDialect', + 'jdbc.driver': 'oracle.jdbc.OracleDriver', + 'jdbc.user' : 'hibernate_orm_test', + 'jdbc.pass' : 'hibernate_orm_test', + 'jdbc.url' : 'jdbc:oracle:thin:@hibernate-testing-oracle-se.ccuzkqo3zqzq.us-east-1.rds.amazonaws.com:1521:ORCL', + 'connection.init_sql' : '' ], // Use ./docker_db.sh oracle_ee to start the database oracle_docker : [ @@ -119,70 +158,80 @@ ext { 'jdbc.driver': 'oracle.jdbc.OracleDriver', 'jdbc.user' : 'c##hibernate_orm_test', 'jdbc.pass' : 'hibernate_orm_test', - 'jdbc.url' : 'jdbc:oracle:thin:@' + dbHost + ':1521/ORCLPDB1.localdomain' + 'jdbc.url' : 'jdbc:oracle:thin:@' + dbHost + ':1521/ORCLPDB1.localdomain', + 'connection.init_sql' : '' ], oracle_ci : [ 'db.dialect' : 'org.hibernate.dialect.Oracle12cDialect', 'jdbc.driver': 'oracle.jdbc.OracleDriver', 'jdbc.user' : 'SYSTEM', 'jdbc.pass' : 'Oracle18', - 'jdbc.url' : 'jdbc:oracle:thin:@' + dbHost + ':1521:XE' + 'jdbc.url' : 'jdbc:oracle:thin:@' + dbHost + ':1521:XE', + 'connection.init_sql' : '' ], oracle_spatial_ci : [ 'db.dialect' : 'org.hibernate.spatial.dialect.oracle.OracleSpatial10gDialect', 'jdbc.driver': 'oracle.jdbc.OracleDriver', 'jdbc.user' : 'SYSTEM', 'jdbc.pass' : 'Oracle18', - 'jdbc.url' : 'jdbc:oracle:thin:@' + dbHost + ':1521:XE' + 'jdbc.url' : 'jdbc:oracle:thin:@' + dbHost + ':1521:XE', + 'connection.init_sql' : '' ], mssql : [ 'db.dialect' : 'org.hibernate.dialect.SQLServer2012Dialect', 'jdbc.driver': 'com.microsoft.sqlserver.jdbc.SQLServerDriver', 'jdbc.user' : 'hibernate_orm_test', 'jdbc.pass' : 'hibernate_orm_test', - 'jdbc.url' : 'jdbc:sqlserver://' + dbHost + ';instance=SQLEXPRESS;databaseName=hibernate_orm_test' + 'jdbc.url' : 'jdbc:sqlserver://' + dbHost + ';instance=SQLEXPRESS;databaseName=hibernate_orm_test', + 'connection.init_sql' : '' ], mssql_ci : [ 'db.dialect' : 'org.hibernate.dialect.SQLServer2012Dialect', 'jdbc.driver': 'com.microsoft.sqlserver.jdbc.SQLServerDriver', 'jdbc.user' : 'sa', 'jdbc.pass' : 'Hibernate_orm_test', - 'jdbc.url' : 'jdbc:sqlserver://' + dbHost + ';databaseName=hibernate_orm_test' + 'jdbc.url' : 'jdbc:sqlserver://' + dbHost + ';databaseName=hibernate_orm_test;sendTimeAsDatetime=false', + 'connection.init_sql' : '' ], mssql_spatial_ci : [ 'db.dialect' : 'org.hibernate.spatial.dialect.sqlserver.SqlServer2012SpatialDialect', 'jdbc.driver': 'com.microsoft.sqlserver.jdbc.SQLServerDriver', 'jdbc.user' : 'sa', 'jdbc.pass' : 'Hibernate_orm_test', - 'jdbc.url' : 'jdbc:sqlserver://' + dbHost + ';databaseName=hibernate_orm_test' + 'jdbc.url' : 'jdbc:sqlserver://' + dbHost + ';databaseName=hibernate_orm_test', + 'connection.init_sql' : '' ], informix : [ 'db.dialect' : 'org.hibernate.dialect.InformixDialect', 'jdbc.driver': 'com.informix.jdbc.IfxDriver', 'jdbc.user' : 'informix', 'jdbc.pass' : 'in4mix', - 'jdbc.url' : 'jdbc:informix-sqli://' + dbHost + ':9088/sysuser:INFORMIXSERVER=dev;user=informix;password=in4mix' + 'jdbc.url' : 'jdbc:informix-sqli://' + dbHost + ':9088/sysuser:INFORMIXSERVER=dev;user=informix;password=in4mix', + 'connection.init_sql' : '' ], db2 : [ 'db.dialect' : 'org.hibernate.dialect.DB2Dialect', 'jdbc.driver': 'com.ibm.db2.jcc.DB2Driver', 'jdbc.user' : 'db2inst1', 'jdbc.pass' : 'db2inst1-pwd', - 'jdbc.url' : 'jdbc:db2://' + dbHost + ':50000/hibern8' + 'jdbc.url' : 'jdbc:db2://' + dbHost + ':50000/hibern8', + 'connection.init_sql' : '' ], db2_ci : [ 'db.dialect' : 'org.hibernate.dialect.DB2Dialect', 'jdbc.driver': 'com.ibm.db2.jcc.DB2Driver', 'jdbc.user' : 'orm_test', 'jdbc.pass' : 'orm_test', - 'jdbc.url' : 'jdbc:db2://' + dbHost + ':50000/orm_test' + 'jdbc.url' : 'jdbc:db2://' + dbHost + ':50000/orm_test', + 'connection.init_sql' : '' ], db2_spatial_ci : [ 'db.dialect' : 'org.hibernate.spatial.dialect.db2.DB2SpatialDialect', 'jdbc.driver': 'com.ibm.db2.jcc.DB2Driver', 'jdbc.user' : 'orm_test', 'jdbc.pass' : 'orm_test', - 'jdbc.url' : 'jdbc:db2://' + dbHost + ':50000/orm_test' + 'jdbc.url' : 'jdbc:db2://' + dbHost + ':50000/orm_test', + 'connection.init_sql' : '' ], hana : [ 'db.dialect' : 'org.hibernate.dialect.HANAColumnStoreDialect', @@ -190,7 +239,8 @@ ext { 'jdbc.user' : 'HIBERNATE_TEST', 'jdbc.pass' : 'H1bernate_test', // Disable prepared statement caching due to https://help.sap.com/viewer/0eec0d68141541d1b07893a39944924e/2.0.04/en-US/78f2163887814223858e4369d18e2847.html - 'jdbc.url' : 'jdbc:sap://' + dbHost + ':30015/?statementCacheSize=0' + 'jdbc.url' : 'jdbc:sap://' + dbHost + ':30015/?statementCacheSize=0', + 'connection.init_sql' : '' ], hana_cloud : [ 'db.dialect' : 'org.hibernate.dialect.HANACloudColumnStoreDialect', @@ -198,7 +248,17 @@ ext { 'jdbc.user' : 'HIBERNATE_TEST', 'jdbc.pass' : 'H1bernate_test', // Disable prepared statement caching due to https://help.sap.com/viewer/0eec0d68141541d1b07893a39944924e/2.0.04/en-US/78f2163887814223858e4369d18e2847.html - 'jdbc.url' : 'jdbc:sap://' + dbHost + ':443/?encrypt=true&validateCertificate=false&statementCacheSize=0' + 'jdbc.url' : 'jdbc:sap://' + dbHost + ':443/?encrypt=true&validateCertificate=false&statementCacheSize=0', + 'connection.init_sql' : '' + ], + hana_jenkins : [ + 'db.dialect' : 'org.hibernate.dialect.HANAColumnStoreDialect', + 'jdbc.driver': 'com.sap.db.jdbc.Driver', + 'jdbc.user' : 'HIBERNATE_TEST', + 'jdbc.pass' : 'H1bernate_test', + // Disable prepared statement caching due to https://help.sap.com/viewer/0eec0d68141541d1b07893a39944924e/2.0.04/en-US/78f2163887814223858e4369d18e2847.html + 'jdbc.url' : 'jdbc:sap://' + dbHost + ':39015/?statementCacheSize=0', + 'connection.init_sql' : '' ], hana_vlad : [ 'db.dialect' : 'org.hibernate.dialect.HANAColumnStoreDialect', @@ -206,7 +266,8 @@ ext { 'jdbc.user' : 'VLAD', 'jdbc.pass' : 'V1ad_test', // Disable prepared statement caching due to https://help.sap.com/viewer/0eec0d68141541d1b07893a39944924e/2.0.04/en-US/78f2163887814223858e4369d18e2847.html - 'jdbc.url' : 'jdbc:sap://' + dbHost + ':39015/?statementCacheSize=0' + 'jdbc.url' : 'jdbc:sap://' + dbHost + ':39015/?statementCacheSize=0', + 'connection.init_sql' : '' ], hana_docker : [ 'db.dialect' : 'org.hibernate.dialect.HANAColumnStoreDialect', @@ -214,7 +275,8 @@ ext { 'jdbc.user' : 'SYSTEM', 'jdbc.pass' : 'H1bernate_test', // Disable prepared statement caching due to https://help.sap.com/viewer/0eec0d68141541d1b07893a39944924e/2.0.04/en-US/78f2163887814223858e4369d18e2847.html - 'jdbc.url' : 'jdbc:sap://' + dbHost + ':39017/?statementCacheSize=0' + 'jdbc.url' : 'jdbc:sap://' + dbHost + ':39017/?statementCacheSize=0', + 'connection.init_sql' : '' ], hana_ci : [ 'db.dialect' : 'org.hibernate.dialect.HANAColumnStoreDialect', @@ -222,7 +284,8 @@ ext { 'jdbc.user' : 'SYSTEM', 'jdbc.pass' : 'H1bernate_test', // Disable prepared statement caching due to https://help.sap.com/viewer/0eec0d68141541d1b07893a39944924e/2.0.04/en-US/78f2163887814223858e4369d18e2847.html - 'jdbc.url' : 'jdbc:sap://' + dbHost + ':39017/?statementCacheSize=0' + 'jdbc.url' : 'jdbc:sap://' + dbHost + ':39017/?statementCacheSize=0', + 'connection.init_sql' : '' ], hana_spatial_ci : [ 'db.dialect' : 'org.hibernate.spatial.dialect.hana.HANASpatialDialect', @@ -230,7 +293,8 @@ ext { 'jdbc.user' : 'SYSTEM', 'jdbc.pass' : 'H1bernate_test', // Disable prepared statement caching due to https://help.sap.com/viewer/0eec0d68141541d1b07893a39944924e/2.0.04/en-US/78f2163887814223858e4369d18e2847.html - 'jdbc.url' : 'jdbc:sap://' + dbHost + ':39017/?statementCacheSize=0' + 'jdbc.url' : 'jdbc:sap://' + dbHost + ':39017/?statementCacheSize=0', + 'connection.init_sql' : '' ], cockroachdb : [ 'db.dialect' : 'org.hibernate.dialect.CockroachDB192Dialect', @@ -239,7 +303,8 @@ ext { 'jdbc.user' : 'root', 'jdbc.pass' : '', // Disable prepared statement caching due to https://www.postgresql.org/message-id/CAEcMXhmmRd4-%2BNQbnjDT26XNdUoXdmntV9zdr8%3DTu8PL9aVCYg%40mail.gmail.com - 'jdbc.url' : 'jdbc:postgresql://' + dbHost + ':26257/defaultdb?sslmode=disable&preparedStatementCacheQueries=0' + 'jdbc.url' : 'jdbc:postgresql://' + dbHost + ':26257/defaultdb?sslmode=disable&preparedStatementCacheQueries=0', + 'connection.init_sql' : '' ], cockroachdb_spatial : [ 'db.dialect' : 'org.hibernate.spatial.dialect.cockroachdb.CockroachDB202SpatialDialect', @@ -248,7 +313,8 @@ ext { 'jdbc.user' : 'root', 'jdbc.pass' : '', // Disable prepared statement caching due to https://www.postgresql.org/message-id/CAEcMXhmmRd4-%2BNQbnjDT26XNdUoXdmntV9zdr8%3DTu8PL9aVCYg%40mail.gmail.com - 'jdbc.url' : 'jdbc:postgresql://' + dbHost + ':26257/defaultdb?sslmode=disable&preparedStatementCacheQueries=0' + 'jdbc.url' : 'jdbc:postgresql://' + dbHost + ':26257/defaultdb?sslmode=disable&preparedStatementCacheQueries=0', + 'connection.init_sql' : '' ] ] } diff --git a/gradle/java-module.gradle b/gradle/java-module.gradle index f4bb0dbfce63..ca8f48168fe1 100644 --- a/gradle/java-module.gradle +++ b/gradle/java-module.gradle @@ -79,17 +79,14 @@ dependencies { testRuntime( libraries.derby ) testRuntime( libraries.hsqldb ) testRuntime( libraries.postgresql ) - testRuntime( libraries.mysql ) - testRuntime( libraries.mariadb ) testRuntime( libraries.mssql ) testRuntime( libraries.informix ) - testRuntime( libraries.hana ) testRuntime( libraries.cockroachdb ) + testRuntime( libraries.oracle ) + testRuntime( libraries.sybase ) asciidoclet 'org.asciidoctor:asciidoclet:1.+' - testRuntime( libraries.oracle ) - // Since both the DB2 driver and HANA have a package "net.jpountz" we have to add dependencies conditionally // This is due to the "no split-packages" requirement of Java 9+ @@ -99,6 +96,12 @@ dependencies { else if ( db.startsWith( 'hana' ) ) { testRuntime( libraries.hana ) } + else if ( db.startsWith( 'mysql' ) ) { + testRuntimeOnly libraries.mysql + } + else if ( db.startsWith( 'mariadb' ) ) { + testRuntimeOnly libraries.mariadb + } // Mac-specific project.ext.toolsJar = file("${System.getProperty('java.home')}/../lib/tools.jar") diff --git a/gradle/libraries.gradle b/gradle/libraries.gradle index ba9581f1d5ab..4c2b17a1b83c 100644 --- a/gradle/libraries.gradle +++ b/gradle/libraries.gradle @@ -8,10 +8,12 @@ // build a map of the dependency artifacts to use. Allows centralized definition of the version of artifacts to // use. In that respect it serves a role similar to in Maven ext { + junit5Version = '5.8.2' + junitVintageVersion = junit5Version + junit4Version = '4.13.2' - junitVersion = '4.13.2' h2Version = '1.4.197' - bytemanVersion = '4.0.16' //Compatible with JDK 17 + bytemanVersion = '4.0.20' //Compatible with JDK 20 jnpVersion = '5.0.6.CR1' hibernateCommonsVersion = '5.1.2.Final' @@ -24,7 +26,7 @@ ext { weldVersion = '3.1.5.Final' jakartaWeldVersion = '4.0.1.SP1' - byteBuddyVersion = '1.12.7' + byteBuddyVersion = '1.12.18' agroalVersion = '1.9' @@ -46,7 +48,7 @@ ext { //GraalVM graalvmVersion = '21.3.0' - micrometerVersion = '1.6.1' + micrometerVersion = '1.9.3' libraries = [ // Ant @@ -89,8 +91,8 @@ ext { // logging logging: 'org.jboss.logging:jboss-logging:3.4.3.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_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_api: "javax.xml.bind:jaxb-api:${jaxbApiVersion}", @@ -116,23 +118,30 @@ ext { // ~~~~~~~~~~~~~~~~~~~~~~~~~~ testing + junit5_api: "org.junit.jupiter:junit-jupiter-api:${junit5Version}", + junit5_jupiter: "org.junit.jupiter:junit-jupiter-engine:${junit5Version}", + junit5_params : "org.junit.jupiter:junit-jupiter-params:${junit5Version}", + junit: "junit:junit:${junit4Version}", + junit5_vintage: "org.junit.vintage:junit-vintage-engine:${junitVintageVersion}", + log4j2: "org.apache.logging.log4j:log4j-core:2.17.1", - junit: "junit:junit:${junitVersion}", + byteman: "org.jboss.byteman:byteman:${bytemanVersion}", byteman_install: "org.jboss.byteman:byteman-install:${bytemanVersion}", byteman_bmunit: "org.jboss.byteman:byteman-bmunit:${bytemanVersion}", h2: "com.h2database:h2:${h2Version}", hsqldb: "org.hsqldb:hsqldb:2.3.2", derby: "org.apache.derby:derby:10.14.2.0", - postgresql: 'org.postgresql:postgresql:42.2.16', + postgresql: 'org.postgresql:postgresql:42.5.0', mysql: 'mysql:mysql-connector-java:8.0.27', mariadb: 'org.mariadb.jdbc:mariadb-java-client:2.2.3', - cockroachdb: 'org.postgresql:postgresql:42.2.8', + cockroachdb: 'org.postgresql:postgresql:42.5.0', oracle: 'com.oracle.database.jdbc:ojdbc8:21.3.0.0', mssql: 'com.microsoft.sqlserver:mssql-jdbc:7.2.1.jre8', db2: 'com.ibm.db2:jcc:11.5.4.0', hana: 'com.sap.cloud.db.jdbc:ngdbc:2.4.59', + sybase: 'net.sourceforge.jtds:jtds:1.3.1', jodaTime: "joda-time:joda-time:${jodaTimeVersion}", @@ -162,7 +171,7 @@ ext { vibur: "org.vibur:vibur-dbcp:25.0", agroal_api: "io.agroal:agroal-api:${agroalVersion}", agroal_pool: "io.agroal:agroal-pool:${agroalVersion}", - micrometer: "io.micrometer:micrometer-core:1.6.1", + micrometer: "io.micrometer:micrometer-core:${micrometerVersion}", atomikos: "com.atomikos:transactions:4.0.6", atomikos_jta: "com.atomikos:transactions-jta:4.0.6", diff --git a/gradle/version.properties b/gradle/version.properties index b2d8dafd4f07..0b454938b8b3 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -hibernateVersion=5.6.5.Final \ No newline at end of file +hibernateVersion=5.6.16-SNAPSHOT \ No newline at end of file diff --git a/hibernate-agroal/src/test/resources/hibernate.properties b/hibernate-agroal/src/test/resources/hibernate.properties index 6b80862911be..da8399b8675f 100644 --- a/hibernate-agroal/src/test/resources/hibernate.properties +++ b/hibernate-agroal/src/test/resources/hibernate.properties @@ -9,6 +9,7 @@ hibernate.connection.driver_class @jdbc.driver@ hibernate.connection.url @jdbc.url@ hibernate.connection.username @jdbc.user@ hibernate.connection.password @jdbc.pass@ +hibernate.connection.init_sql @connection.init_sql@ hibernate.jdbc.batch_size 10 hibernate.connection.provider_class AgroalConnectionProvider diff --git a/hibernate-c3p0/src/test/resources/hibernate.properties b/hibernate-c3p0/src/test/resources/hibernate.properties index 0d39da782e64..715af2a80a52 100644 --- a/hibernate-c3p0/src/test/resources/hibernate.properties +++ b/hibernate-c3p0/src/test/resources/hibernate.properties @@ -9,6 +9,7 @@ hibernate.connection.driver_class @jdbc.driver@ hibernate.connection.url @jdbc.url@ hibernate.connection.username @jdbc.user@ hibernate.connection.password @jdbc.pass@ +hibernate.connection.init_sql @connection.init_sql@ hibernate.connection.pool_size 5 hibernate.c3p0.min_size 50 diff --git a/hibernate-core-jakarta/hibernate-core-jakarta.gradle b/hibernate-core-jakarta/hibernate-core-jakarta.gradle index 09e3e1bd5dc8..802fea183165 100644 --- a/hibernate-core-jakarta/hibernate-core-jakarta.gradle +++ b/hibernate-core-jakarta/hibernate-core-jakarta.gradle @@ -94,6 +94,8 @@ processResources.enabled false compileTestJava.enabled false processTestResources.enabled false jar.enabled false +javadocJar.enabled false +sourcesJar.enabled false ext { transformedJarName = project(':hibernate-core').tasks.jar.archiveFileName.get().replaceAll( 'hibernate-core', 'hibernate-core-jakarta' ) @@ -123,6 +125,26 @@ task transformJar(type: JakartaJarTransformation) { targetJar tasks.jar.archiveFile.get().asFile } +task transformSourcesJar(type: JakartaJarTransformation) { + description 'Transforms the hibernate-core sources jar using the JakartaTransformer tool' + + dependsOn project(':hibernate-core').tasks.sourcesJar + mustRunAfter project(':hibernate-core').tasks.sourcesJar + + sourceJar project(':hibernate-core').tasks.sourcesJar.archiveFile + targetJar tasks.sourcesJar.archiveFile.get().asFile +} + +task transformJavadocJar(type: JakartaJarTransformation) { + description 'Transforms the hibernate-core javadoc jar using the JakartaTransformer tool' + + dependsOn project(':hibernate-core').tasks.javadocJar + mustRunAfter project(':hibernate-core').tasks.javadocJar + + sourceJar project(':hibernate-core').tasks.javadocJar.archiveFile + targetJar tasks.javadocJar.archiveFile.get().asFile +} + configurations { [apiElements, runtimeElements].each { it.outgoing.artifacts.removeIf { @@ -131,6 +153,12 @@ configurations { it.outgoing.artifact(tasks.transformJar.targetJar) { builtBy tasks.transformJar } + it.outgoing.artifact(tasks.transformSourcesJar.targetJar) { + builtBy tasks.transformSourcesJar + } + it.outgoing.artifact(tasks.transformJavadocJar.targetJar) { + builtBy tasks.transformJavadocJar + } } } diff --git a/hibernate-core/src/main/antlr/hql-sql.g b/hibernate-core/src/main/antlr/hql-sql.g index 142348258022..c1ab78645d32 100644 --- a/hibernate-core/src/main/antlr/hql-sql.g +++ b/hibernate-core/src/main/antlr/hql-sql.g @@ -261,6 +261,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; } @@ -746,12 +755,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); @@ -776,6 +788,12 @@ addrExpr! [ boolean root ] } ; +fkRef + : #( r:FK_REF p:propertyRef ) { + #p = lookupProperty( #p, false, isInSelect() ); + } + ; + addrExprLhs : addrExpr [ false ] ; @@ -797,8 +815,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 3f2782263107..a8b32453c6e4 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"; @@ -722,7 +723,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 | { validateSoftKeyword("size") && LA(2) == OPEN }? collectionSizeFunction | identPrimary ( options {greedy=true;} : DOT^ "class" )? @@ -731,6 +733,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() ); @@ -824,6 +832,7 @@ identPrimary #identPrimary = #( [ENTRY], path ); } } + | (DOT^ FK_REF) )? // Also allow special 'aggregate functions' such as count(), avg(), etc. | aggregate diff --git a/hibernate-core/src/main/antlr/sql-gen.g b/hibernate-core/src/main/antlr/sql-gen.g index b59667c79658..a5cfb13ee592 100644 --- a/hibernate-core/src/main/antlr/sql-gen.g +++ b/hibernate-core/src/main/antlr/sql-gen.g @@ -509,6 +509,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/FetchNotFoundException.java b/hibernate-core/src/main/java/org/hibernate/FetchNotFoundException.java new file mode 100644 index 000000000000..42f729c1f2ce --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/FetchNotFoundException.java @@ -0,0 +1,43 @@ +/* + * 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; + +import java.util.Locale; +import javax.persistence.EntityNotFoundException; + +/** + * Exception for {@link org.hibernate.annotations.NotFoundAction#EXCEPTION} + * + * @see org.hibernate.annotations.NotFound + * + * @author Steve Ebersole + */ +public class FetchNotFoundException extends EntityNotFoundException { + private final String entityName; + private final Object identifier; + + public FetchNotFoundException(String entityName, Object identifier) { + super( + String.format( + Locale.ROOT, + "Entity `%s` with identifier value `%s` does not exist", + entityName, + identifier + ) + ); + this.entityName = entityName; + this.identifier = identifier; + } + + public String getEntityName() { + return entityName; + } + + public Object getIdentifier() { + return identifier; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/Hibernate.java b/hibernate-core/src/main/java/org/hibernate/Hibernate.java index 9f4fea0184d5..22cd1e646f89 100644 --- a/hibernate-core/src/main/java/org/hibernate/Hibernate.java +++ b/hibernate-core/src/main/java/org/hibernate/Hibernate.java @@ -10,16 +10,18 @@ import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor; import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; -import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.HibernateIterator; import org.hibernate.engine.jdbc.LobCreator; import org.hibernate.engine.jdbc.spi.JdbcServices; -import org.hibernate.engine.spi.PersistentAttributeInterceptable; import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.proxy.HibernateProxy; import org.hibernate.proxy.LazyInitializer; +import org.hibernate.collection.spi.LazyInitializable; + +import static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptable; +import static org.hibernate.engine.internal.ManagedTypeHelper.isPersistentAttributeInterceptable; /** *
    @@ -61,12 +63,11 @@ public static void initialize(Object proxy) throws HibernateException { if ( proxy instanceof HibernateProxy ) { ( (HibernateProxy) proxy ).getHibernateLazyInitializer().initialize(); } - else if ( proxy instanceof PersistentCollection ) { - ( (PersistentCollection) proxy ).forceInitialization(); + else if ( proxy instanceof LazyInitializable ) { + ( (LazyInitializable) proxy ).forceInitialization(); } - else if ( proxy instanceof PersistentAttributeInterceptable ) { - final PersistentAttributeInterceptable interceptable = (PersistentAttributeInterceptable) proxy; - final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor(); + else if ( isPersistentAttributeInterceptable( proxy ) ) { + final PersistentAttributeInterceptor interceptor = asPersistentAttributeInterceptable( proxy ).$$_hibernate_getInterceptor(); if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { ( (EnhancementAsProxyLazinessInterceptor) interceptor ).forceInitialize( proxy, null ); } @@ -84,15 +85,15 @@ 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(); + else if ( isPersistentAttributeInterceptable( proxy ) ) { + final PersistentAttributeInterceptor interceptor = asPersistentAttributeInterceptable( proxy ).$$_hibernate_getInterceptor(); if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { return false; } return true; } - else if ( proxy instanceof PersistentCollection ) { - return ( (PersistentCollection) proxy ).wasInitialized(); + else if ( proxy instanceof LazyInitializable ) { + return ( (LazyInitializable) proxy ).wasInitialized(); } else { return true; @@ -200,8 +201,9 @@ public static boolean isPropertyInitialized(Object proxy, String propertyName) { entity = proxy; } - if ( entity instanceof PersistentAttributeInterceptable ) { - PersistentAttributeInterceptor interceptor = ( (PersistentAttributeInterceptable) entity ).$$_hibernate_getInterceptor(); + + if ( isPersistentAttributeInterceptable( entity ) ) { + PersistentAttributeInterceptor interceptor = asPersistentAttributeInterceptable( entity ).$$_hibernate_getInterceptor(); if ( interceptor instanceof BytecodeLazyAttributeInterceptor ) { return ( (BytecodeLazyAttributeInterceptor) interceptor ).isAttributeLoaded( propertyName ); } diff --git a/hibernate-core/src/main/java/org/hibernate/Session.java b/hibernate-core/src/main/java/org/hibernate/Session.java index c673743e22b2..9bb66e1c8cac 100644 --- a/hibernate-core/src/main/java/org/hibernate/Session.java +++ b/hibernate-core/src/main/java/org/hibernate/Session.java @@ -793,7 +793,22 @@ public interface Session extends SharedSessionContract, EntityManager, Hibernate * @return the entity name */ String getEntityName(Object object); - + + /** + * Return a reference to the persistent instance with the same identity as the given + * instance, which might be detached, making the assumption that the instance is still + * persistent in the database. This method never results in access to the underlying + * data store, and thus might return a proxy that is initialized on-demand, when a + * non-identifier method is accessed. + * + * @param object a detached persistent instance + * + * @return the persistent instance or proxy + */ + default T getReference(T object) { + throw new IllegalStateException( "getReference(Object) is not implemented in " + getClass() ); + } + /** * Create an {@link IdentifierLoadAccess} instance to retrieve the specified entity type by * primary key. @@ -1156,6 +1171,16 @@ interface LockRequest { org.hibernate.query.Query createNamedQuery(String name, Class resultType); + /** + * Create a {@link NativeQuery} instance for the given SQL query string. + * + * @param queryString The SQL query + * + * @return The query instance for manipulation and execution + * + * @deprecated (since 5.2) use {@link #createNativeQuery(String)} instead + */ + @Deprecated @Override NativeQuery createSQLQuery(String queryString); } 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 41bc9f9e653b..7dbed1ba0d37 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; @@ -32,6 +33,7 @@ import org.hibernate.persister.entity.EntityPersister; import org.hibernate.stat.internal.StatsHelper; import org.hibernate.stat.spi.StatisticsImplementor; +import org.hibernate.tuple.entity.EntityMetamodel; import org.hibernate.type.TypeHelper; /** @@ -239,9 +241,21 @@ public void execute() throws HibernateException { entry.postUpdate( instance, state, nextVersion ); } + if ( entry.getStatus() == Status.DELETED ) { + final EntityMetamodel entityMetamodel = persister.getEntityMetamodel(); + final boolean isImpliedOptimisticLocking = !entityMetamodel.isVersioned() + && entityMetamodel.getOptimisticLockStyle().isAllOrDirty(); + if ( isImpliedOptimisticLocking && entry.getLoadedState() != null ) { + // The entity will be deleted and because we are going to create a delete statement that uses + // all the state values in the where clause, the entry state needs to be updated otherwise the statement execution will + // not delete any row (see HHH-15218). + entry.postUpdate( instance, state, nextVersion ); + } + } + final StatisticsImplementor statistics = factory.getStatistics(); if ( persister.canWriteToCache() ) { - if ( persister.isCacheInvalidationRequired() || entry.getStatus() != Status.MANAGED ) { + if ( isCacheInvalidationRequired( persister, session ) || entry.getStatus() != Status.MANAGED ) { persister.getCacheAccessStrategy().remove( session, ck ); } else if ( session.getCacheMode().isPutEnabled() ) { @@ -275,6 +289,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 IGNORE + return persister.isCacheInvalidationRequired() || session.getCacheMode() == CacheMode.GET || session.getCacheMode() == CacheMode.IGNORE; + } + protected boolean cacheUpdate(EntityPersister persister, Object previousVersion, Object ck) { final SharedSessionContractImplementor session = getSession(); try { diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/FetchMode.java b/hibernate-core/src/main/java/org/hibernate/annotations/FetchMode.java index fa1e03a7645a..b9ca4abc83c2 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/FetchMode.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/FetchMode.java @@ -7,8 +7,8 @@ package org.hibernate.annotations; /** - * Fetch options on associations. Defines more of the "how" of fetching, whereas JPA {@link javax.persistence.FetchType} - * focuses on the "when". + * Defines how the association should be fetched, compared to + * {@link javax.persistence.FetchType} which defines when it should be fetched * * @author Emmanuel Bernard */ @@ -16,13 +16,27 @@ public enum FetchMode { /** * Use a secondary select for each individual entity, collection, or join load. */ - SELECT, + SELECT( org.hibernate.FetchMode.SELECT ), /** * Use an outer join to load the related entities, collections or joins. */ - JOIN, + JOIN( org.hibernate.FetchMode.JOIN ), /** - * Available for collections only.  When accessing a non-initialized collection, this fetch mode will trigger loading all elements of all collections of the same role for all owners associated with the persistence context using a single secondary select. + * Available for collections only. + * + * When accessing a non-initialized collection, this fetch mode will trigger + * loading all elements of all collections of the same role for all owners + * associated with the persistence context using a single secondary select. */ - SUBSELECT + SUBSELECT( org.hibernate.FetchMode.SELECT ); + + private final org.hibernate.FetchMode hibernateFetchMode; + + FetchMode(org.hibernate.FetchMode hibernateFetchMode) { + this.hibernateFetchMode = hibernateFetchMode; + } + + public org.hibernate.FetchMode getHibernateFetchMode() { + return hibernateFetchMode; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/NotFoundAction.java b/hibernate-core/src/main/java/org/hibernate/annotations/NotFoundAction.java index c6e4e9622d29..906d65024096 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/NotFoundAction.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/NotFoundAction.java @@ -6,20 +6,36 @@ */ package org.hibernate.annotations; +import org.hibernate.FetchNotFoundException; /** - * Possible actions when an associated entity is not found in the database. Often seen with "legacy" foreign-key - * schemes which do not use {@code NULL} to indicate a missing reference, instead using a "magic value". + * Possible actions when the database contains a non-null fk with no + * matching target. This also implies that there are no physical + * foreign-key constraints on the database. * + * As an example, consider a typical Customer/Order model. These actions apply + * when a non-null `orders.customer_fk` value does not have a corresponding value + * in `customers.id`. + * + * Generally this will occur in 2 scenarios:
      + *
    • the associated data has been deleted
    • + *
    • the model uses special "magic" values to indicate null
    • + *
    + * + * @author Steve Ebersole * @author Emmanuel Bernard */ public enum NotFoundAction { /** - * Raise an exception when an element is not found (default and recommended). + * Throw an exception when the association is not found (default and recommended). + * + * @see FetchNotFoundException */ EXCEPTION, + /** - * Ignore the element when not found in database. + * Ignore the association when not found in database. Effectively treats the + * association as null, despite the non-null foreign-key value. */ IGNORE } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/archive/scan/spi/ClassFileArchiveEntryHandler.java b/hibernate-core/src/main/java/org/hibernate/boot/archive/scan/spi/ClassFileArchiveEntryHandler.java index 8679accff6c2..a4866f1a3ce0 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/archive/scan/spi/ClassFileArchiveEntryHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/archive/scan/spi/ClassFileArchiveEntryHandler.java @@ -61,8 +61,9 @@ public void handleEntry(ArchiveEntry entry, ArchiveContext context) { private ClassDescriptor toClassDescriptor(ArchiveEntry entry) { try (InputStream inputStream = entry.getStreamAccess().accessInputStream()) { Indexer indexer = new Indexer(); - ClassInfo classInfo = indexer.index( inputStream ); + indexer.index( inputStream ); Index index = indexer.complete(); + ClassInfo classInfo = index.getKnownClasses().iterator().next(); return toClassDescriptor( classInfo, index, entry ); } catch (IOException e) { 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 89a749a2d154..a8b2f122a0ef 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 @@ -67,19 +67,15 @@ else if ( CFG_XSD_MAPPING.matches( namespace ) ) { ); return openUrlStream( HBM_DTD_MAPPING.getMappedLocalUrl() ); } - else if ( LEGACY_HBM_DTD_MAPPING.matches( publicID, systemID ) ) { - DeprecationLogger.DEPRECATION_LOGGER.recognizedObsoleteHibernateNamespace( - LEGACY_HBM_DTD_MAPPING.getIdentifierBase(), - HBM_DTD_MAPPING.getIdentifierBase() - ); + else if ( ALTERNATE_MAPPING_DTD.matches( publicID, systemID ) ) { log.debug( - "Recognized legacy hibernate-mapping identifier; attempting to resolve on classpath under org/hibernate/" + "Recognized alternate hibernate-mapping identifier; attempting to resolve on classpath under org/hibernate/" ); - return openUrlStream( HBM_DTD_MAPPING.getMappedLocalUrl() ); + return openUrlStream( ALTERNATE_MAPPING_DTD.getMappedLocalUrl() ); } - else if ( LEGACY2_HBM_DTD_MAPPING.matches( publicID, systemID ) ) { + else if ( LEGACY_HBM_DTD_MAPPING.matches( publicID, systemID ) ) { DeprecationLogger.DEPRECATION_LOGGER.recognizedObsoleteHibernateNamespace( - LEGACY2_HBM_DTD_MAPPING.getIdentifierBase(), + LEGACY_HBM_DTD_MAPPING.getIdentifierBase(), HBM_DTD_MAPPING.getIdentifierBase() ); log.debug( @@ -93,6 +89,12 @@ else if ( CFG_DTD_MAPPING.matches( publicID, systemID ) ) { ); return openUrlStream( CFG_DTD_MAPPING.getMappedLocalUrl() ); } + else if ( ALTERNATE_CFG_DTD.matches( publicID, systemID ) ) { + log.debug( + "Recognized alternate hibernate-configuration identifier; attempting to resolve on classpath under org/hibernate/" + ); + return openUrlStream( ALTERNATE_CFG_DTD.getMappedLocalUrl() ); + } else if ( LEGACY_CFG_DTD_MAPPING.matches( publicID, systemID ) ) { DeprecationLogger.DEPRECATION_LOGGER.recognizedObsoleteHibernateNamespace( LEGACY_CFG_DTD_MAPPING.getIdentifierBase(), @@ -158,7 +160,7 @@ 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 */ @@ -174,7 +176,7 @@ private InputStream resolveInLocalNamespace(String path) { "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" @@ -191,27 +193,32 @@ private InputStream resolveInLocalNamespace(String path) { ); public static final DtdMapping HBM_DTD_MAPPING = new DtdMapping( - "http://www.hibernate.org/dtd/hibernate-mapping", + "www.hibernate.org/dtd/hibernate-mapping", "org/hibernate/hibernate-mapping-3.0.dtd" ); - public static final DtdMapping LEGACY_HBM_DTD_MAPPING = new DtdMapping( - "http://www.hibernate.org/dtd/hibernate-mapping", + public static final DtdMapping ALTERNATE_MAPPING_DTD = new DtdMapping( + "hibernate.org/dtd/hibernate-mapping", "org/hibernate/hibernate-mapping-3.0.dtd" ); - public static final DtdMapping LEGACY2_HBM_DTD_MAPPING = new DtdMapping( - "http://hibernate.sourceforge.net/hibernate-mapping", + public static final DtdMapping LEGACY_HBM_DTD_MAPPING = new DtdMapping( + "hibernate.sourceforge.net/hibernate-mapping", "org/hibernate/hibernate-mapping-3.0.dtd" ); public static final DtdMapping CFG_DTD_MAPPING = new DtdMapping( - "http://www.hibernate.org/dtd/hibernate-configuration", + "www.hibernate.org/dtd/hibernate-configuration", + "org/hibernate/hibernate-configuration-3.0.dtd" + ); + + public static final DtdMapping ALTERNATE_CFG_DTD = new DtdMapping( + "hibernate.org/dtd/hibernate-configuration", "org/hibernate/hibernate-configuration-3.0.dtd" ); public static final DtdMapping LEGACY_CFG_DTD_MAPPING = new DtdMapping( - "http://hibernate.sourceforge.net/hibernate-configuration", + "hibernate.sourceforge.net/hibernate-configuration", "org/hibernate/hibernate-configuration-3.0.dtd" ); @@ -235,27 +242,31 @@ public URL getMappedLocalUrl() { } public static class DtdMapping { - private final String identifierBase; + private final String httpBase; + private final String httpsBase; private final URL localSchemaUrl; public DtdMapping(String identifierBase, String resourceName) { - this.identifierBase = identifierBase; + this.httpBase = "http://" + identifierBase; + this.httpsBase = "https://" + identifierBase; this.localSchemaUrl = LocalSchemaLocator.resolveLocalSchemaUrl( resourceName ); } public String getIdentifierBase() { - return identifierBase; + return httpBase; } public boolean matches(String publicId, String systemId) { if ( publicId != null ) { - if ( publicId.startsWith( identifierBase ) ) { + if ( publicId.startsWith( httpBase ) + || publicId.startsWith( httpsBase ) ) { return true; } } if ( systemId != null ) { - if ( systemId.startsWith( identifierBase ) ) { + if ( systemId.startsWith( httpBase ) + || systemId.startsWith( httpsBase ) ) { return true; } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/naming/Identifier.java b/hibernate-core/src/main/java/org/hibernate/boot/model/naming/Identifier.java index be697f9be446..667c189bf577 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/naming/Identifier.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/naming/Identifier.java @@ -75,6 +75,64 @@ public static Identifier toIdentifier(String text, boolean quote) { } } + /** + * Means to generate an {@link Identifier} instance from its simple text form. + *

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

    + * If passed text is surrounded in quote markers, the generated Identifier + * is considered quoted. Quote markers include back-ticks (`), + * double-quotes (") and brackets ([ and ]). + * + * @param text The text form + * @param quote Whether to quote unquoted text forms + * @param quoteOnNonIdentifierChar Controls whether to treat the result as quoted if text contains characters that are invalid for identifiers + * + * @return The identifier form, or {@code null} if text was {@code null} + */ + public static Identifier toIdentifier(String text, boolean quote, boolean quoteOnNonIdentifierChar) { + if ( StringHelper.isEmpty( text ) ) { + return null; + } + int start = 0; + int end = text.length(); + while ( start < end ) { + if ( !Character.isWhitespace( text.charAt( start ) ) ) { + break; + } + start++; + } + while ( start < end ) { + if ( !Character.isWhitespace( text.charAt( end - 1 ) ) ) { + break; + } + end--; + } + if ( isQuoted( text, start, end ) ) { + start++; + end--; + quote = true; + } + else if ( quoteOnNonIdentifierChar && !quote ) { + // Check the letters to determine if we must quote the text + char c = text.charAt( start ); + if ( !Character.isLetter( c ) && c != '_' ) { + // SQL identifiers must begin with a letter or underscore + quote = true; + } + else { + for ( int i = start + 1; i < end; i++ ) { + c = text.charAt( i ); + if ( !Character.isLetterOrDigit( c ) && c != '_' ) { + quote = true; + break; + } + } + } + } + return new Identifier( text.substring( start, end ), quote ); + } + /** * Is the given identifier text considered quoted. The following patterns are * recognized as quoted:

      @@ -96,6 +154,20 @@ public static boolean isQuoted(String name) { || ( name.startsWith( "\"" ) && name.endsWith( "\"" ) ); } + public static boolean isQuoted(String name, int start, int end) { + if ( start + 2 < end ) { + switch ( name.charAt( start ) ) { + case '`': + return name.charAt( end - 1 ) == '`'; + case '[': + return name.charAt( end - 1 ) == ']'; + case '"': + return name.charAt( end - 1 ) == '"'; + } + } + return false; + } + /** * Constructs an identifier instance. * diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/AuxiliaryDatabaseObject.java b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/AuxiliaryDatabaseObject.java index 60c54fcb334b..fe91498b11c0 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/AuxiliaryDatabaseObject.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/AuxiliaryDatabaseObject.java @@ -56,7 +56,9 @@ default String[] sqlCreateStrings(SqlStringGenerationContext context) { * @param dialect The dialect for which to generate the SQL creation strings * * @return the SQL strings for creating the database object. - * @deprecated Implement {@link #sqlCreateStrings(SqlStringGenerationContext)} instead. + * @deprecated Hibernate ORM may never call this method, + * and implementations cannot properly handle default catalogs/schemas. + * Call/implement {@link #sqlCreateStrings(SqlStringGenerationContext)} instead. */ @Deprecated default String[] sqlCreateStrings(Dialect dialect) { @@ -80,7 +82,9 @@ default String[] sqlDropStrings(SqlStringGenerationContext context) { * @param dialect The dialect for which to generate the SQL drop strings * * @return the SQL strings for dropping the database object. - * @deprecated Implement {@link #sqlDropStrings(SqlStringGenerationContext)} instead. + * @deprecated Hibernate ORM may never call this method, + * and implementations cannot properly handle default catalogs/schemas. + * Call/implement {@link #sqlDropStrings(SqlStringGenerationContext)} instead. */ @Deprecated default String[] sqlDropStrings(Dialect dialect) { 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 df59ef7466f7..618931f1d04b 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 @@ -9,6 +9,7 @@ import java.util.Set; import org.hibernate.boot.model.naming.Identifier; +import org.hibernate.boot.model.relational.internal.SqlStringGenerationContextImpl; import org.hibernate.dialect.Dialect; import org.hibernate.internal.util.StringHelper; @@ -76,19 +77,37 @@ public SimpleAuxiliaryDatabaseObject( } @Override + @Deprecated public String[] sqlCreateStrings(Dialect dialect) { + // Implemented exclusively for backwards compatibility for callers other than Hibernate ORM. + // This is not called by Hibernate ORM and will not take into account + // default catalog/schema set through configuration properties. + return sqlCreateStrings( SqlStringGenerationContextImpl.forBackwardsCompatibility( dialect, null, null ) ); + } + + @Override + public String[] sqlCreateStrings(SqlStringGenerationContext context) { final String[] copy = new String[createStrings.length]; for ( int i = 0, max =createStrings.length; i * Note that the Identifiers returned from this helper already account for auto-quoting. + * + * @deprecated Use {@link #toIdentifier(String)} instead. */ + @Deprecated IdentifierHelper getIdentifierHelper(); + /** + * Generate an Identifier instance from its simple name as obtained from mapping + * information. + *

      + * Note that Identifiers returned from here may be implicitly quoted based on + * 'globally quoted identifiers' or based on reserved words. + * + * @param text The text form of a name as obtained from mapping information. + * + * @return The identifier form of the name. + */ + Identifier toIdentifier(String text); + /** * @return The default catalog, used for table/sequence names that do not explicitly mention a catalog. * May be {@code null}. diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/internal/SqlStringGenerationContextImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/internal/SqlStringGenerationContextImpl.java index 1f6fa93d3a4c..e61331500201 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/internal/SqlStringGenerationContextImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/internal/SqlStringGenerationContextImpl.java @@ -6,6 +6,7 @@ */ package org.hibernate.boot.model.relational.internal; +import java.sql.SQLException; import java.util.Map; import org.hibernate.boot.model.naming.Identifier; @@ -17,13 +18,18 @@ import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.cfg.AvailableSettings; import org.hibernate.dialect.Dialect; +import org.hibernate.engine.jdbc.env.internal.QualifiedObjectNameFormatterStandardImpl; import org.hibernate.engine.jdbc.env.spi.IdentifierHelper; +import org.hibernate.engine.jdbc.env.spi.IdentifierHelperBuilder; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.engine.jdbc.env.spi.NameQualifierSupport; import org.hibernate.engine.jdbc.env.spi.QualifiedObjectNameFormatter; +import org.jboss.logging.Logger; + public class SqlStringGenerationContextImpl implements SqlStringGenerationContext { + private static final Logger log = Logger.getLogger( SqlStringGenerationContextImpl.class ); /** * @param jdbcEnvironment The JDBC environment, to extract the dialect, identifier helper, etc. @@ -67,6 +73,37 @@ public static SqlStringGenerationContext fromExplicit(JdbcEnvironment jdbcEnviro return new SqlStringGenerationContextImpl( jdbcEnvironment, actualDefaultCatalog, actualDefaultSchema ); } + /** + * @param dialect The dialect to use. + * @param defaultCatalog The default catalog to use. + * @param defaultSchema The default schema to use. + * @return An {@link SqlStringGenerationContext}. + * @deprecated Only use for backwards compatibility in deprecated methods. + * New methods should take the {@link SqlStringGenerationContext} as an argument, + * and should not need to create their own context. + */ + @Deprecated + public static SqlStringGenerationContext forBackwardsCompatibility(Dialect dialect, String defaultCatalog, String defaultSchema) { + NameQualifierSupport nameQualifierSupport = dialect.getNameQualifierSupport(); + if ( nameQualifierSupport == null ) { + // assume both catalogs and schemas are supported + nameQualifierSupport = NameQualifierSupport.BOTH; + } + QualifiedObjectNameFormatter qualifiedObjectNameFormatter = + new QualifiedObjectNameFormatterStandardImpl( nameQualifierSupport ); + + Identifier actualDefaultCatalog = null; + if ( nameQualifierSupport.supportsCatalogs() ) { + actualDefaultCatalog = Identifier.toIdentifier( defaultCatalog ); + } + Identifier actualDefaultSchema = null; + if ( nameQualifierSupport.supportsSchemas() ) { + actualDefaultSchema = Identifier.toIdentifier( defaultSchema ); + } + return new SqlStringGenerationContextImpl( dialect, null, qualifiedObjectNameFormatter, + actualDefaultCatalog, actualDefaultSchema ); + } + public static SqlStringGenerationContext forTests(JdbcEnvironment jdbcEnvironment) { return forTests( jdbcEnvironment, null, null ); } @@ -87,9 +124,17 @@ public static SqlStringGenerationContext forTests(JdbcEnvironment jdbcEnvironmen @SuppressWarnings("deprecation") private SqlStringGenerationContextImpl(JdbcEnvironment jdbcEnvironment, Identifier defaultCatalog, Identifier defaultSchema) { - this.dialect = jdbcEnvironment.getDialect(); - this.identifierHelper = jdbcEnvironment.getIdentifierHelper(); - this.qualifiedObjectNameFormatter = jdbcEnvironment.getQualifiedObjectNameFormatter(); + this( jdbcEnvironment.getDialect(), jdbcEnvironment.getIdentifierHelper(), + jdbcEnvironment.getQualifiedObjectNameFormatter(), + defaultCatalog, defaultSchema ); + } + + private SqlStringGenerationContextImpl(Dialect dialect, IdentifierHelper identifierHelper, + QualifiedObjectNameFormatter qualifiedObjectNameFormatter, + Identifier defaultCatalog, Identifier defaultSchema) { + this.dialect = dialect; + this.identifierHelper = identifierHelper; + this.qualifiedObjectNameFormatter = qualifiedObjectNameFormatter; this.defaultCatalog = defaultCatalog; this.defaultSchema = defaultSchema; } @@ -104,6 +149,11 @@ public IdentifierHelper getIdentifierHelper() { return identifierHelper; } + @Override + public Identifier toIdentifier(String text) { + return identifierHelper != null ? identifierHelper.toIdentifier( text ) : Identifier.toIdentifier( text ); + } + @Override public Identifier getDefaultCatalog() { return defaultCatalog; 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 e0f7310847d1..3052ee32c0c9 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 @@ -2948,7 +2948,7 @@ private Identifier determineCatalogName(TableSpecificationSource tableSpecSource return database.toIdentifier( tableSpecSource.getExplicitCatalogName() ); } else { - return database.toIdentifier( metadataBuildingContext.getMappingDefaults().getImplicitCatalogName() ); + return null; } } 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 d142264bd9c7..849ebc93fd8c 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 @@ -34,6 +34,7 @@ import org.hibernate.bytecode.internal.bytebuddy.ByteBuddyState; import org.hibernate.engine.spi.CompositeOwner; import org.hibernate.engine.spi.CompositeTracker; +import org.hibernate.engine.spi.EnhancedEntity; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.ExtendedSelfDirtinessTracker; import org.hibernate.engine.spi.Managed; @@ -68,6 +69,7 @@ public class EnhancerImpl implements Enhancer { private static final CoreMessageLogger log = CoreLogging.messageLogger( Enhancer.class ); + private static final AnnotationDescription TRANSIENT_ANNOTATION = AnnotationDescription.Builder.ofType( Transient.class ).build(); protected final ByteBuddyEnhancementContext enhancementContext; private final ByteBuddyState byteBuddyState; @@ -154,12 +156,14 @@ private DynamicType.Builder doEnhance(DynamicType.Builder builder, TypeDes log.debugf( "Skipping enhancement of [%s]: already enhanced", managedCtClass.getName() ); return null; } + final EnhancementStatus es = new EnhancementStatus( managedCtClass.getName() ); if ( enhancementContext.isEntityClass( managedCtClass ) ) { log.debugf( "Enhancing [%s] as Entity", managedCtClass.getName() ); builder = builder.implement( ManagedEntity.class ) .defineMethod( EnhancerConstants.ENTITY_INSTANCE_GETTER_NAME, Object.class, Visibility.PUBLIC ) .intercept( FixedValue.self() ); + es.enabledInterfaceManagedEntity(); builder = addFieldWithGetterAndSetter( builder, @@ -183,7 +187,7 @@ private DynamicType.Builder doEnhance(DynamicType.Builder builder, TypeDes EnhancerConstants.NEXT_SETTER_NAME ); - builder = addInterceptorHandling( builder, managedCtClass ); + builder = addInterceptorHandling( builder, managedCtClass, es ); if ( enhancementContext.doDirtyCheckingInline( managedCtClass ) ) { List collectionFields = collectCollectionFields( managedCtClass ); @@ -191,7 +195,7 @@ private DynamicType.Builder doEnhance(DynamicType.Builder builder, TypeDes 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( TRANSIENT_ANNOTATION ) .defineMethod( EnhancerConstants.TRACKER_CHANGER_NAME, void.class, Visibility.PUBLIC ) .withParameters( String.class ) .intercept( implementationTrackChange ) @@ -206,13 +210,15 @@ private DynamicType.Builder doEnhance(DynamicType.Builder builder, TypeDes .intercept( implementationSuspendDirtyTracking ) .defineMethod( EnhancerConstants.TRACKER_COLLECTION_GET_NAME, CollectionTracker.class, Visibility.PUBLIC ) .intercept( implementationGetCollectionTrackerWithoutCollections ); + es.enabledInterfaceSelfDirtinessTracker(); } else { + //TODO es.enableInterfaceExtendedSelfDirtinessTracker ? Careful with consequences.. builder = builder.implement( ExtendedSelfDirtinessTracker.class ) .defineField( EnhancerConstants.TRACKER_FIELD_NAME, DirtyTracker.class, FieldPersistence.TRANSIENT, Visibility.PRIVATE ) - .annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() ) + .annotateField( TRANSIENT_ANNOTATION ) .defineField( EnhancerConstants.TRACKER_COLLECTION_NAME, CollectionTracker.class, FieldPersistence.TRANSIENT, Visibility.PRIVATE ) - .annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() ) + .annotateField( TRANSIENT_ANNOTATION ) .defineMethod( EnhancerConstants.TRACKER_CHANGER_NAME, void.class, Visibility.PUBLIC ) .withParameters( String.class ) .intercept( implementationTrackChange ) @@ -302,13 +308,13 @@ private DynamicType.Builder doEnhance(DynamicType.Builder builder, TypeDes } } - return createTransformer( managedCtClass ).applyTo( builder ); + return createTransformer( managedCtClass ).applyTo( builder, es ); } else if ( enhancementContext.isCompositeClass( managedCtClass ) ) { log.debugf( "Enhancing [%s] as Composite", managedCtClass.getName() ); builder = builder.implement( ManagedComposite.class ); - builder = addInterceptorHandling( builder, managedCtClass ); + builder = addInterceptorHandling( builder, managedCtClass, es ); if ( enhancementContext.doDirtyCheckingInline( managedCtClass ) ) { builder = builder.implement( CompositeTracker.class ) @@ -318,7 +324,7 @@ else if ( enhancementContext.isCompositeClass( managedCtClass ) ) { FieldPersistence.TRANSIENT, Visibility.PRIVATE ) - .annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() ) + .annotateField( TRANSIENT_ANNOTATION ) .defineMethod( EnhancerConstants.TRACKER_COMPOSITE_SET_OWNER, void.class, @@ -335,17 +341,17 @@ else if ( enhancementContext.isCompositeClass( managedCtClass ) ) { .intercept( implementationClearOwner ); } - return createTransformer( managedCtClass ).applyTo( builder ); + return createTransformer( managedCtClass ).applyTo( builder, es ); } else if ( enhancementContext.isMappedSuperclassClass( managedCtClass ) ) { log.debugf( "Enhancing [%s] as MappedSuperclass", managedCtClass.getName() ); builder = builder.implement( ManagedMappedSuperclass.class ); - return createTransformer( managedCtClass ).applyTo( builder ); + return createTransformer( managedCtClass ).applyTo( builder, es ); } else if ( enhancementContext.doExtendedEnhancement( managedCtClass ) ) { log.debugf( "Extended enhancement of [%s]", managedCtClass.getName() ); - return createTransformer( managedCtClass ).applyExtended( builder ); + return createTransformer( managedCtClass ).applyExtended( builder, es ); } else { log.debugf( "Skipping enhancement of [%s]: not entity or composite", managedCtClass.getName() ); @@ -367,12 +373,13 @@ private boolean alreadyEnhanced(TypeDescription managedCtClass) { return false; } - private DynamicType.Builder addInterceptorHandling(DynamicType.Builder builder, TypeDescription managedCtClass) { + private DynamicType.Builder addInterceptorHandling(DynamicType.Builder builder, TypeDescription managedCtClass, EnhancementStatus es) { // interceptor handling is only needed if class has lazy-loadable attributes if ( enhancementContext.hasLazyLoadableAttributes( managedCtClass ) ) { log.debugf( "Weaving in PersistentAttributeInterceptable implementation on [%s]", managedCtClass.getName() ); builder = builder.implement( PersistentAttributeInterceptable.class ); + es.enabledInterfacePersistentAttributeInterceptable(); builder = addFieldWithGetterAndSetter( builder, @@ -394,7 +401,7 @@ private static DynamicType.Builder addFieldWithGetterAndSetter( String setterName) { return builder .defineField( fieldName, type, Visibility.PRIVATE, FieldPersistence.TRANSIENT ) - .annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() ) + .annotateField( TRANSIENT_ANNOTATION ) .defineMethod( getterName, type, Visibility.PUBLIC ) .intercept( FieldAccessor.ofField( fieldName ) ) .defineMethod( setterName, void.class, Visibility.PUBLIC ) @@ -592,4 +599,53 @@ void setClassNameAndBytes(String className, byte[] bytes) { this.resolution = new Resolution.Explicit( bytes); } } + + /** + * Attempt to keep track of which interfaces are being applied, + * so to attempt dodging the performance implications of for https://bugs.openjdk.org/browse/JDK-8180450 + * We're optimising for the case in which entities are fully enhanced. + */ + final static class EnhancementStatus { + + private final String typeName; + private boolean managedEntity = false; + private boolean selfDirtynessTracker = false; + private boolean persistentAttributeInterceptable = false; + private boolean applied = false; + + public EnhancementStatus(String typeName) { + this.typeName = typeName; + } + + public void enabledInterfaceManagedEntity() { + this.managedEntity = true; + } + + public void enabledInterfaceSelfDirtinessTracker() { + this.selfDirtynessTracker = true; + } + + public void enabledInterfacePersistentAttributeInterceptable() { + this.persistentAttributeInterceptable = true; + } + + public DynamicType.Builder applySuperInterfaceOptimisations(DynamicType.Builder builder) { + if ( applied ) { + throw new IllegalStateException("Should not apply super-interface optimisations twice"); + } + else { + applied = true; + if ( managedEntity && persistentAttributeInterceptable && selfDirtynessTracker ) { + log.debugf( "Applying Enhancer optimisations for type [%s]; adding EnhancedEntity as additional marker.", typeName ); + return builder.implement( EnhancedEntity.class ); + } + else { + log.debugf( "Applying Enhancer optimisations for type [%s]; NOT enabling EnhancedEntity as additional marker.", typeName ); + } + //TODO consider applying a marker for other combinations of interfaces as well? + } + return builder; + } + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/InlineDirtyCheckerEqualsHelper.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/InlineDirtyCheckerEqualsHelper.java new file mode 100644 index 000000000000..098db0b8051a --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/InlineDirtyCheckerEqualsHelper.java @@ -0,0 +1,132 @@ +/* + * 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.enhance.internal.bytebuddy; + +import java.util.Objects; + +import org.hibernate.engine.spi.PersistentAttributeInterceptable; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; + +public final class InlineDirtyCheckerEqualsHelper { + + public static boolean areEquals( + PersistentAttributeInterceptable persistentAttributeInterceptable, + String fieldName, + Object a, + Object b) { + final PersistentAttributeInterceptor persistentAttributeInterceptor = persistentAttributeInterceptable.$$_hibernate_getInterceptor(); + if ( persistentAttributeInterceptor != null + && !persistentAttributeInterceptor.isAttributeLoaded( fieldName ) ) { + return false; + } + return Objects.deepEquals( a, b ); + } + + public static boolean areEquals( + PersistentAttributeInterceptable persistentAttributeInterceptable, + String fieldName, + boolean a, + boolean b) { + final PersistentAttributeInterceptor persistentAttributeInterceptor = persistentAttributeInterceptable.$$_hibernate_getInterceptor(); + if ( persistentAttributeInterceptor != null + && !persistentAttributeInterceptor.isAttributeLoaded( fieldName ) ) { + return false; + } + return a == b; + } + + public static boolean areEquals( + PersistentAttributeInterceptable persistentAttributeInterceptable, + String fieldName, + byte a, + byte b) { + final PersistentAttributeInterceptor persistentAttributeInterceptor = persistentAttributeInterceptable.$$_hibernate_getInterceptor(); + if ( persistentAttributeInterceptor != null + && !persistentAttributeInterceptor.isAttributeLoaded( fieldName ) ) { + return false; + } + return a == b; + } + + public static boolean areEquals( + PersistentAttributeInterceptable persistentAttributeInterceptable, + String fieldName, + short a, + short b) { + final PersistentAttributeInterceptor persistentAttributeInterceptor = persistentAttributeInterceptable.$$_hibernate_getInterceptor(); + if ( persistentAttributeInterceptor != null + && !persistentAttributeInterceptor.isAttributeLoaded( fieldName ) ) { + return false; + } + return a == b; + } + + public static boolean areEquals( + PersistentAttributeInterceptable persistentAttributeInterceptable, + String fieldName, + char a, + char b) { + final PersistentAttributeInterceptor persistentAttributeInterceptor = persistentAttributeInterceptable.$$_hibernate_getInterceptor(); + if ( persistentAttributeInterceptor != null + && !persistentAttributeInterceptor.isAttributeLoaded( fieldName ) ) { + return false; + } + return a == b; + } + + public static boolean areEquals( + PersistentAttributeInterceptable persistentAttributeInterceptable, + String fieldName, + int a, + int b) { + final PersistentAttributeInterceptor persistentAttributeInterceptor = persistentAttributeInterceptable.$$_hibernate_getInterceptor(); + if ( persistentAttributeInterceptor != null + && !persistentAttributeInterceptor.isAttributeLoaded( fieldName ) ) { + return false; + } + return a == b; + } + + public static boolean areEquals( + PersistentAttributeInterceptable persistentAttributeInterceptable, + String fieldName, + long a, + long b) { + final PersistentAttributeInterceptor persistentAttributeInterceptor = persistentAttributeInterceptable.$$_hibernate_getInterceptor(); + if ( persistentAttributeInterceptor != null + && !persistentAttributeInterceptor.isAttributeLoaded( fieldName ) ) { + return false; + } + return a == b; + } + + public static boolean areEquals( + PersistentAttributeInterceptable persistentAttributeInterceptable, + String fieldName, + float a, + float b) { + final PersistentAttributeInterceptor persistentAttributeInterceptor = persistentAttributeInterceptable.$$_hibernate_getInterceptor(); + if ( persistentAttributeInterceptor != null + && !persistentAttributeInterceptor.isAttributeLoaded( fieldName ) ) { + return false; + } + return a == b; + } + + public static boolean areEquals( + PersistentAttributeInterceptable persistentAttributeInterceptable, + String fieldName, + double a, + double b) { + final PersistentAttributeInterceptor persistentAttributeInterceptor = persistentAttributeInterceptable.$$_hibernate_getInterceptor(); + if ( persistentAttributeInterceptor != null + && !persistentAttributeInterceptor.isAttributeLoaded( fieldName ) ) { + return false; + } + return a == b; + } +} 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 1c61641c2940..6306e52436bb 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 @@ -15,6 +15,7 @@ import org.hibernate.bytecode.enhance.internal.bytebuddy.EnhancerImpl.AnnotatedFieldDescription; import org.hibernate.bytecode.enhance.spi.EnhancerConstants; +import org.hibernate.engine.spi.PersistentAttributeInterceptable; import net.bytebuddy.ClassFileVersion; import net.bytebuddy.asm.Advice; @@ -31,16 +32,27 @@ final class InlineDirtyCheckingHandler implements Implementation, ByteCodeAppender { + private static final String HELPER_TYPE_NAME = Type.getInternalName( InlineDirtyCheckerEqualsHelper.class ); + private static final Type PE_INTERCEPTABLE_TYPE = Type.getType( PersistentAttributeInterceptable.class ); + private static final Type OBJECT_TYPE = Type.getType( Object.class ); + private static final Type STRING_TYPE = Type.getType( String.class ); + private final Implementation delegate; private final TypeDescription managedCtClass; private final FieldDescription.InDefinedShape persistentField; + private final boolean applyLazyCheck; - private InlineDirtyCheckingHandler(Implementation delegate, TypeDescription managedCtClass, FieldDescription.InDefinedShape persistentField) { + private InlineDirtyCheckingHandler( + Implementation delegate, + TypeDescription managedCtClass, + FieldDescription.InDefinedShape persistentField, + boolean applyLazyCheck) { this.delegate = delegate; this.managedCtClass = managedCtClass; this.persistentField = persistentField; + this.applyLazyCheck = applyLazyCheck; } static Implementation wrap( @@ -57,8 +69,12 @@ 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(), + enhancementContext.hasLazyLoadableAttributes( managedCtClass ) + ); } if ( enhancementContext.isCompositeClass( persistentField.getType().asErasure() ) @@ -97,6 +113,11 @@ public Size apply( Context implementationContext, MethodDescription instrumentedMethod) { // if (arg != field) { + + if ( applyLazyCheck ) { + methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 ); + methodVisitor.visitLdcInsn( persistentField.getName() ); + } methodVisitor.visitVarInsn( Type.getType( persistentField.getType().asErasure().getDescriptor() ).getOpcode( Opcodes.ILOAD ), 1 ); methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 ); if ( persistentField.getDeclaringType().asErasure().equals( managedCtClass ) ) { @@ -117,30 +138,70 @@ public Size apply( ); } int branchCode; - if ( persistentField.getType().isPrimitive() ) { - if ( persistentField.getType().represents( long.class ) ) { - methodVisitor.visitInsn( Opcodes.LCMP ); - } - else if ( persistentField.getType().represents( float.class ) ) { - methodVisitor.visitInsn( Opcodes.FCMPL ); - } - else if ( persistentField.getType().represents( double.class ) ) { - methodVisitor.visitInsn( Opcodes.DCMPL ); + if ( applyLazyCheck ) { + if ( persistentField.getType().isPrimitive() ) { + final Type fieldType = Type.getType( persistentField.getDescriptor() ); + methodVisitor.visitMethodInsn( + Opcodes.INVOKESTATIC, + HELPER_TYPE_NAME, + "areEquals", + Type.getMethodDescriptor( + Type.BOOLEAN_TYPE, + PE_INTERCEPTABLE_TYPE, + STRING_TYPE, + fieldType, + fieldType + ), + false + ); } else { - methodVisitor.visitInsn( Opcodes.ISUB ); + methodVisitor.visitMethodInsn( + Opcodes.INVOKESTATIC, + HELPER_TYPE_NAME, + "areEquals", + Type.getMethodDescriptor( + Type.BOOLEAN_TYPE, + PE_INTERCEPTABLE_TYPE, + STRING_TYPE, + OBJECT_TYPE, + OBJECT_TYPE + ), + false + ); } - branchCode = Opcodes.IFEQ; + branchCode = Opcodes.IFNE; } else { - methodVisitor.visitMethodInsn( - Opcodes.INVOKESTATIC, - Type.getInternalName( Objects.class ), - "deepEquals", - Type.getMethodDescriptor( Type.getType( boolean.class ), Type.getType( Object.class ), Type.getType( Object.class ) ), - false - ); - branchCode = Opcodes.IFNE; + if ( persistentField.getType().isPrimitive() ) { + if ( persistentField.getType().represents( long.class ) ) { + methodVisitor.visitInsn( Opcodes.LCMP ); + } + else if ( persistentField.getType().represents( float.class ) ) { + methodVisitor.visitInsn( Opcodes.FCMPL ); + } + else if ( persistentField.getType().represents( double.class ) ) { + methodVisitor.visitInsn( Opcodes.DCMPL ); + } + else { + methodVisitor.visitInsn( Opcodes.ISUB ); + } + branchCode = Opcodes.IFEQ; + } + else { + methodVisitor.visitMethodInsn( + Opcodes.INVOKESTATIC, + Type.getInternalName( Objects.class ), + "deepEquals", + Type.getMethodDescriptor( + Type.BOOLEAN_TYPE, + OBJECT_TYPE, + OBJECT_TYPE + ), + false + ); + branchCode = Opcodes.IFNE; + } } Label skip = new Label(); methodVisitor.visitJumpInsn( branchCode, skip ); @@ -151,7 +212,7 @@ else if ( persistentField.getType().represents( double.class ) ) { Opcodes.INVOKEVIRTUAL, managedCtClass.getInternalName(), EnhancerConstants.TRACKER_CHANGER_NAME, - Type.getMethodDescriptor( Type.getType( void.class ), Type.getType( String.class ) ), + Type.getMethodDescriptor( Type.VOID_TYPE, STRING_TYPE ), false ); // } @@ -159,7 +220,7 @@ else if ( persistentField.getType().represents( double.class ) ) { if ( implementationContext.getClassFileVersion().isAtLeast( ClassFileVersion.JAVA_V6 ) ) { methodVisitor.visitFrame( Opcodes.F_SAME, 0, null, 0, null ); } - return new Size( 1 + 2 * persistentField.getType().asErasure().getStackSize().getSize(), instrumentedMethod.getStackSize() ); + return new Size( 3 + 2 * persistentField.getType().asErasure().getStackSize().getSize(), instrumentedMethod.getStackSize() ); } @Override 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 287ce6b793e4..94c2e65a36db 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 @@ -91,6 +91,12 @@ public static PersistentAttributeTransformer collectPersistentFields( ByteBuddyEnhancementContext enhancementContext, TypePool classPool) { List persistentFieldList = new ArrayList<>(); + // HHH-10646 Add fields inherited from @MappedSuperclass + // HHH-10981 There is no need to do it for @MappedSuperclass + // HHH-15505 This needs to be done first so that fields with the same name in the mappedsuperclass and entity are handled correctly + if ( !enhancementContext.isMappedSuperclassClass( managedCtClass ) ) { + persistentFieldList.addAll( collectInheritPersistentFields( managedCtClass, enhancementContext ) ); + } 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() ) ) { @@ -101,11 +107,6 @@ public static PersistentAttributeTransformer collectPersistentFields( persistentFieldList.add( annotatedField ); } } - // HHH-10646 Add fields inherited from @MappedSuperclass - // HHH-10981 There is no need to do it for @MappedSuperclass - if ( !enhancementContext.isMappedSuperclassClass( managedCtClass ) ) { - persistentFieldList.addAll( collectInheritPersistentFields( managedCtClass, enhancementContext ) ); - } AnnotatedFieldDescription[] orderedFields = enhancementContext.order( persistentFieldList.toArray( new AnnotatedFieldDescription[0] ) ); log.debugf( "Persistent fields for entity %s: %s", managedCtClass.getName(), Arrays.toString( orderedFields ) ); @@ -200,7 +201,8 @@ private AnnotatedFieldDescription getEnhancedField(String owner, String name, St return null; } - DynamicType.Builder applyTo(DynamicType.Builder builder) { + DynamicType.Builder applyTo(DynamicType.Builder builder, EnhancerImpl.EnhancementStatus es) { + builder = es.applySuperInterfaceOptimisations(builder); boolean compositeOwner = false; builder = builder.visit( new AsmVisitorWrapper.ForDeclaredMethods().invokable( NOT_HIBERNATE_GENERATED, this ) ); @@ -255,7 +257,7 @@ DynamicType.Builder applyTo(DynamicType.Builder builder) { } if ( enhancementContext.doExtendedEnhancement( managedCtClass ) ) { - builder = applyExtended( builder ); + builder = applyExtended( builder, es ); } return builder; @@ -304,7 +306,7 @@ private Implementation fieldWriterImplementation(AnnotatedFieldDescription enhan } } - DynamicType.Builder applyExtended(DynamicType.Builder builder) { + DynamicType.Builder applyExtended(DynamicType.Builder builder, EnhancerImpl.EnhancementStatus es) { AsmVisitorWrapper.ForDeclaredMethods.MethodVisitorWrapper enhancer = new FieldAccessEnhancer( managedCtClass, enhancementContext, classPool ); return builder.visit( new AsmVisitorWrapper.ForDeclaredMethods().invokable( NOT_HIBERNATE_GENERATED, enhancer ) ); } 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 index a4fd9a7593e0..5ff6b950c8c3 100644 --- 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 @@ -36,6 +36,7 @@ public interface BytecodeLazyAttributeInterceptor extends SessionAssociableInter */ void attributeInitialized(String name); + @Override boolean isAttributeLoaded(String fieldName); boolean hasAnyUninitializedAttributes(); 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 index e5a65e356579..005889668d2c 100644 --- 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 @@ -72,8 +72,9 @@ public EnhancementAsProxyLazinessInterceptor( 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() ); + // because the pre-computed update statement contains even not dirty properties and so we need all the values + // we have to initialise it even if it's versioned to fetch the current version + initializeBeforeWrite = !( inLineDirtyChecking && entityPersister.getEntityMetamodel().isDynamicUpdate() ) || entityPersister.isVersioned(); status = Status.UNINITIALIZED; } 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 1b084cccd223..ecdbade9de96 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 @@ -17,11 +17,15 @@ import org.hibernate.bytecode.enhance.spi.CollectionTracker; import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; import org.hibernate.collection.spi.PersistentCollection; +import org.hibernate.engine.internal.ManagedTypeHelper; 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 static org.hibernate.engine.internal.ManagedTypeHelper.asSelfDirtinessTracker; +import static org.hibernate.engine.internal.ManagedTypeHelper.isSelfDirtinessTracker; + /** * Interceptor that loads attributes lazily * @@ -30,6 +34,8 @@ */ public class LazyAttributeLoadingInterceptor extends AbstractLazyLoadInterceptor { private final Object identifier; + + //N.B. this Set needs to be treated as immutable private final Set lazyFields; private Set initializedLazyFields; @@ -40,7 +46,8 @@ public LazyAttributeLoadingInterceptor( SharedSessionContractImplementor session) { super( entityName, session ); this.identifier = identifier; - this.lazyFields = lazyFields; + //Important optimisation to not actually do a Map lookup for entities which don't have any lazy fields at all: + this.lazyFields = org.hibernate.internal.util.collections.CollectionHelper.toSmallSet( lazyFields ); } @Override @@ -121,7 +128,7 @@ public boolean isAttributeLoaded(String fieldName) { } private boolean isLazyAttribute(String fieldName) { - return lazyFields == null || lazyFields.contains( fieldName ); + return lazyFields.contains( fieldName ); } private boolean isInitializedLazyField(String fieldName) { @@ -129,7 +136,7 @@ private boolean isInitializedLazyField(String fieldName) { } public boolean hasAnyUninitializedAttributes() { - if ( lazyFields == null || lazyFields.isEmpty() ) { + if ( lazyFields.isEmpty() ) { return false; } @@ -152,13 +159,14 @@ public String toString() { } private void takeCollectionSizeSnapshot(Object target, String fieldName, Object value) { - if ( value instanceof Collection && target instanceof SelfDirtinessTracker ) { + if ( value instanceof Collection && isSelfDirtinessTracker( target ) ) { // This must be called first, so that we remember that there is a collection out there, // even if we don't know its size (see below). - CollectionTracker tracker = ( (SelfDirtinessTracker) target ).$$_hibernate_getCollectionTracker(); + final SelfDirtinessTracker trackerAsSDT = asSelfDirtinessTracker( target ); + CollectionTracker tracker = trackerAsSDT.$$_hibernate_getCollectionTracker(); if ( tracker == null ) { - ( (SelfDirtinessTracker) target ).$$_hibernate_clearDirtyAttributes(); - tracker = ( (SelfDirtinessTracker) target ).$$_hibernate_getCollectionTracker(); + trackerAsSDT.$$_hibernate_clearDirtyAttributes(); + tracker = trackerAsSDT.$$_hibernate_getCollectionTracker(); } if ( value instanceof PersistentCollection && !( (PersistentCollection) value ).wasInitialized() ) { 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 90c5b78a0890..593773e6fda2 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 @@ -24,6 +24,7 @@ import java.util.function.Function; import org.hibernate.HibernateException; +import org.hibernate.bytecode.enhance.spi.EnhancerConstants; import org.hibernate.bytecode.spi.BasicProxyFactory; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.proxy.ProxyConfiguration; @@ -188,6 +189,10 @@ public Unloaded make(Function> makeProxyFun return make( makeProxyFunction.apply( byteBuddy ) ); } + public Unloaded make(TypePool typePool, Function> makeProxyFunction) { + return make( typePool, makeProxyFunction.apply( byteBuddy ) ); + } + private Unloaded make(DynamicType.Builder builder) { return make( null, builder ); } @@ -248,7 +253,7 @@ public static class ProxyDefinitionHelpers { private final ElementMatcher groovyGetMetaClassFilter; private final ElementMatcher virtualNotFinalizerFilter; - private final ElementMatcher hibernateGeneratedMethodFilter; + private final ElementMatcher proxyNonInterceptedMethodFilter; private final MethodDelegation delegateToInterceptorDispatcherMethodDelegation; private final FieldAccessor.PropertyConfigurable interceptorFieldAccessor; @@ -256,7 +261,11 @@ 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() ); + this.proxyNonInterceptedMethodFilter = nameStartsWith( "$$_hibernate_" ).and( isVirtual() ) + // HHH-15090: Don't apply extended enhancement reader/writer methods to the proxy; + // those need to be executed on the actual entity. + .and( not( nameStartsWith( EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX ) ) ) + .and( not( nameStartsWith( EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX ) ) ); PrivilegedAction delegateToInterceptorDispatcherMethodDelegationPrivilegedAction = new PrivilegedAction() { @@ -294,8 +303,8 @@ public ElementMatcher getVirtualNotFinalizerFilter() return virtualNotFinalizerFilter; } - public ElementMatcher getHibernateGeneratedMethodFilter() { - return hibernateGeneratedMethodFilter; + public ElementMatcher getProxyNonInterceptedMethodFilter() { + return proxyNonInterceptedMethodFilter; } public MethodDelegation getDelegateToInterceptorDispatcherMethodDelegation() { 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 81793fb23d46..491af0356634 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 @@ -178,13 +178,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 @@ -199,13 +200,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 9ce34807a33b..ae218d636f83 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 @@ -491,6 +491,10 @@ public QueryResultsCache getQueryResultsCacheStrictly(String regionName) { return null; } + if ( regionName == null || regionName.equals( getDefaultQueryResultsCache().getRegion().getName() ) ) { + return getDefaultQueryResultsCache(); + } + return namedQueryResultsCacheMap.get( regionName ); } diff --git a/hibernate-core/src/main/java/org/hibernate/cache/internal/NoCachingTransactionSynchronizationImpl.java b/hibernate-core/src/main/java/org/hibernate/cache/internal/NoCachingTransactionSynchronizationImpl.java index b6914f3a484a..920e64158601 100644 --- a/hibernate-core/src/main/java/org/hibernate/cache/internal/NoCachingTransactionSynchronizationImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/cache/internal/NoCachingTransactionSynchronizationImpl.java @@ -16,4 +16,9 @@ public class NoCachingTransactionSynchronizationImpl extends AbstractCacheTransa public NoCachingTransactionSynchronizationImpl(RegionFactory regionFactory) { super( regionFactory ); } + + @Override + public long getCachingTimestamp() { + throw new UnsupportedOperationException( "Method not supported when 2LC is not enabled" ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/CacheTransactionSynchronization.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/CacheTransactionSynchronization.java index 415abdc70c48..8d98aab96af2 100644 --- a/hibernate-core/src/main/java/org/hibernate/cache/spi/CacheTransactionSynchronization.java +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/CacheTransactionSynchronization.java @@ -48,9 +48,29 @@ public interface CacheTransactionSynchronization { * * @implSpec This "timestamp" need not be related to timestamp in the Java * Date/millisecond sense. It just needs to be an incrementing value. + * + * @deprecated Use {@link CacheTransactionSynchronization#getCachingTimestamp()} instead. */ + @Deprecated long getCurrentTransactionStartTimestamp(); + /** + * What is the start time of this context object? + * + * @apiNote If not currently joined to a transaction, the timestamp from + * the last transaction is safe to use. If not ever/yet joined to a + * transaction, a timestamp at the time the Session/CacheTransactionSynchronization + * were created should be returned. + * + * @implSpec This "timestamp" need not be related to timestamp in the Java + * Date/millisecond sense. It just needs to be an incrementing value. + * + * An UnsupportedOperationException is thrown if the Second Level Cache has not been enabled + */ + default long getCachingTimestamp(){ + return getCurrentTransactionStartTimestamp(); + } + /** * Callback that owning Session has become joined to a resource transaction. * 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 3881e9912105..49a059e61628 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java @@ -170,6 +170,7 @@ import org.hibernate.mapping.ToOne; import org.hibernate.mapping.UnionSubclass; +import static org.hibernate.cfg.BinderHelper.isEmptyAnnotationValue; import static org.hibernate.internal.CoreLogging.messageLogger; /** @@ -691,7 +692,7 @@ else if ( InheritanceType.JOINED.equals( inheritanceState.getType() ) ) { SimpleValue key = new DependantValue( context, jsc.getTable(), jsc.getIdentifier() ); jsc.setKey( key ); ForeignKey fk = clazzToProcess.getAnnotation( ForeignKey.class ); - if ( fk != null && !BinderHelper.isEmptyAnnotationValue( fk.name() ) ) { + if ( fk != null && !isEmptyAnnotationValue( fk.name() ) ) { key.setForeignKeyName( fk.name() ); } else { @@ -1371,7 +1372,7 @@ private static void bindTypeDef(TypeDef defAnn, MetadataBuildingContext context) params.setProperty( param.name(), param.value() ); } - if ( BinderHelper.isEmptyAnnotationValue( defAnn.name() ) && defAnn.defaultForType().equals( void.class ) ) { + if ( isEmptyAnnotationValue( defAnn.name() ) && defAnn.defaultForType().equals( void.class ) ) { throw new AnnotationException( "Either name or defaultForType (or both) attribute should be set in TypeDef having typeClass " + defAnn.typeClass().getName() @@ -1379,7 +1380,7 @@ private static void bindTypeDef(TypeDef defAnn, MetadataBuildingContext context) } final String typeBindMessageF = "Binding type definition: %s"; - if ( !BinderHelper.isEmptyAnnotationValue( defAnn.name() ) ) { + if ( !isEmptyAnnotationValue( defAnn.name() ) ) { if ( LOG.isDebugEnabled() ) { LOG.debugf( typeBindMessageF, defAnn.name() ); } @@ -1794,10 +1795,12 @@ private static void processElementAnnotations( ); } + final NotFound notFound = property.getAnnotation( NotFound.class ); + final NotFoundAction notFoundAction = notFound == null ? null : notFound.action(); + final boolean hasNotFound = notFoundAction != null; + checkFetchModeAgainstNotFound( propertyHolder.getEntityName(), property.getName(), hasNotFound, ann.fetch() ); + Cascade hibernateCascade = property.getAnnotation( Cascade.class ); - NotFound notFound = property.getAnnotation( NotFound.class ); - boolean ignoreNotFound = notFound != null && notFound.action().equals( NotFoundAction.IGNORE ); - matchIgnoreNotFoundWithFetchType(propertyHolder.getEntityName(), property.getName(), ignoreNotFound, ann.fetch()); OnDelete onDeleteAnn = property.getAnnotation( OnDelete.class ); boolean onDeleteCascade = onDeleteAnn != null && OnDeleteAction.CASCADE.equals( onDeleteAnn.action() ); JoinTable assocTable = propertyHolder.getJoinTable( property ); @@ -1813,15 +1816,14 @@ private static void processElementAnnotations( // 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 ); + final boolean mandatory = !ann.optional() + || property.isAnnotationPresent( Id.class ) + || ( property.isAnnotationPresent( MapsId.class ) && !hasNotFound ); bindManyToOne( getCascadeStrategy( ann.cascade(), hibernateCascade, false, forcePersist ), joinColumns, !mandatory, - ignoreNotFound, + notFoundAction, onDeleteCascade, ToOneBinder.getTargetEntity( inferredData, context ), propertyHolder, @@ -1849,9 +1851,12 @@ else if ( property.isAnnotationPresent( OneToOne.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 ); + final Cascade hibernateCascade = property.getAnnotation( Cascade.class ); + final NotFound notFound = property.getAnnotation( NotFound.class ); + final NotFoundAction notFoundAction = notFound == null ? null : notFound.action(); + final boolean hasNotFound = notFoundAction != null; + checkFetchModeAgainstNotFound( propertyHolder.getEntityName(), property.getName(), hasNotFound, ann.fetch() ); + // 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 @@ -1859,14 +1864,14 @@ else if ( property.isAnnotationPresent( OneToOne.class ) ) { // 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 ); - matchIgnoreNotFoundWithFetchType(propertyHolder.getEntityName(), property.getName(), ignoreNotFound, ann.fetch()); - OnDelete onDeleteAnn = property.getAnnotation( OnDelete.class ); - boolean onDeleteCascade = onDeleteAnn != null && OnDeleteAction.CASCADE.equals( onDeleteAnn.action() ); - JoinTable assocTable = propertyHolder.getJoinTable( property ); + final boolean mandatory = !ann.optional() + || property.isAnnotationPresent( Id.class ) + || ( property.isAnnotationPresent( MapsId.class ) && !hasNotFound ); + checkFetchModeAgainstNotFound( propertyHolder.getEntityName(), property.getName(), hasNotFound, ann.fetch() ); + + final OnDelete onDeleteAnn = property.getAnnotation( OnDelete.class ); + final boolean onDeleteCascade = onDeleteAnn != null && OnDeleteAction.CASCADE.equals( onDeleteAnn.action() ); + final JoinTable assocTable = propertyHolder.getJoinTable( property ); if ( assocTable != null ) { Join join = propertyHolder.addJoin( assocTable, false ); for ( Ejb3JoinColumn joinColumn : joinColumns ) { @@ -1878,7 +1883,8 @@ else if ( property.isAnnotationPresent( OneToOne.class ) ) { joinColumns, !mandatory, getFetchMode( ann.fetch() ), - ignoreNotFound, onDeleteCascade, + notFoundAction, + onDeleteCascade, ToOneBinder.getTargetEntity( inferredData, context ), propertyHolder, inferredData, @@ -2572,13 +2578,13 @@ private static void bindJoinedTableAssociation( if ( jpaIndexes != null && jpaIndexes.length > 0 ) { associationTableBinder.setJpaIndex( jpaIndexes ); } - if ( !BinderHelper.isEmptyAnnotationValue( schema ) ) { + if ( !isEmptyAnnotationValue( schema ) ) { associationTableBinder.setSchema( schema ); } - if ( !BinderHelper.isEmptyAnnotationValue( catalog ) ) { + if ( !isEmptyAnnotationValue( catalog ) ) { associationTableBinder.setCatalog( catalog ); } - if ( !BinderHelper.isEmptyAnnotationValue( tableName ) ) { + if ( !isEmptyAnnotationValue( tableName ) ) { associationTableBinder.setName( tableName ); } associationTableBinder.setUniqueConstraints( uniqueConstraints ); @@ -3040,7 +3046,7 @@ private static void bindManyToOne( String cascadeStrategy, Ejb3JoinColumn[] columns, boolean optional, - boolean ignoreNotFound, + NotFoundAction notFoundAction, boolean cascadeOnDelete, XClass targetEntity, PropertyHolder propertyHolder, @@ -3060,7 +3066,7 @@ private static void bindManyToOne( final XProperty property = inferredData.getProperty(); defineFetchingStrategy( value, property ); //value.setFetchMode( fetchMode ); - value.setIgnoreNotFound( ignoreNotFound ); + value.setNotFoundAction( notFoundAction ); value.setCascadeDeleteEnabled( cascadeOnDelete ); //value.setLazy( fetchMode != FetchMode.JOIN ); if ( !optional ) { @@ -3090,7 +3096,7 @@ private static void bindManyToOne( } if ( property.isAnnotationPresent( ManyToOne.class ) && joinColumn != null - && ! BinderHelper.isEmptyAnnotationValue( joinColumn.name() ) + && ! isEmptyAnnotationValue( joinColumn.name() ) && joinColumn.name().equals( columnName ) && !property.isAnnotationPresent( MapsId.class ) ) { hasSpecjManyToOne = true; @@ -3153,11 +3159,24 @@ else if (hasSpecjManyToOne) { propertyBinder.setXToMany( true ); final Property boundProperty = propertyBinder.makePropertyAndBind(); + boundProperty.setOptional( optional && isNullable( joinColumns, joinColumn ) ); + } + + private static boolean isNullable(JoinColumns joinColumns, JoinColumn joinColumn) { if ( joinColumn != null ) { - boundProperty.setOptional( joinColumn.nullable() && optional ); + return joinColumn.nullable(); + } + else if ( joinColumns != null ) { + final JoinColumn[] col = joinColumns.value(); + for ( int i = 0; i < col.length; i++ ) { + if ( joinColumns.value()[i].nullable() ) { + return true; + } + } + return false; } else { - boundProperty.setOptional( optional ); + return true; } } @@ -3166,6 +3185,8 @@ protected static void defineFetchingStrategy(ToOne toOne, XProperty property) { Fetch fetch = property.getAnnotation( Fetch.class ); ManyToOne manyToOne = property.getAnnotation( ManyToOne.class ); OneToOne oneToOne = property.getAnnotation( OneToOne.class ); + NotFound notFound = property.getAnnotation( NotFound.class ); + FetchType fetchType; if ( manyToOne != null ) { fetchType = manyToOne.fetch(); @@ -3178,7 +3199,12 @@ else if ( oneToOne != null ) { "Define fetch strategy on a property not annotated with @OneToMany nor @OneToOne" ); } - if ( lazy != null ) { + + if ( notFound != null ) { + toOne.setLazy( false ); + toOne.setUnwrapProxy( true ); + } + else if ( lazy != null ) { toOne.setLazy( !( lazy.value() == LazyToOneOption.FALSE ) ); toOne.setUnwrapProxy( ( lazy.value() == LazyToOneOption.NO_PROXY ) ); } @@ -3187,6 +3213,7 @@ else if ( oneToOne != null ) { toOne.setUnwrapProxy( fetchType != FetchType.LAZY ); toOne.setUnwrapProxyImplicit( true ); } + if ( fetch != null ) { if ( fetch.value() == org.hibernate.annotations.FetchMode.JOIN ) { toOne.setFetchMode( FetchMode.JOIN ); @@ -3213,7 +3240,7 @@ private static void bindOneToOne( Ejb3JoinColumn[] joinColumns, boolean optional, FetchMode fetchMode, - boolean ignoreNotFound, + NotFoundAction notFoundAction, boolean cascadeOnDelete, XClass targetEntity, PropertyHolder propertyHolder, @@ -3257,7 +3284,7 @@ private static void bindOneToOne( } } } - if ( trueOneToOne || mapToPK || !BinderHelper.isEmptyAnnotationValue( mappedBy ) ) { + if ( trueOneToOne || mapToPK || !isEmptyAnnotationValue( mappedBy ) ) { //is a true one-to-one //FIXME referencedColumnName ignored => ordering may fail. OneToOneSecondPass secondPass = new OneToOneSecondPass( @@ -3267,7 +3294,7 @@ private static void bindOneToOne( propertyHolder, inferredData, targetEntity, - ignoreNotFound, + notFoundAction, cascadeOnDelete, optional, cascadeStrategy, @@ -3278,19 +3305,25 @@ private static void bindOneToOne( secondPass.doSecondPass( context.getMetadataCollector().getEntityBindingMap() ); } else { - context.getMetadataCollector().addSecondPass( - secondPass, - BinderHelper.isEmptyAnnotationValue( mappedBy ) - ); + context.getMetadataCollector().addSecondPass( secondPass, isEmptyAnnotationValue( mappedBy ) ); } } else { //has a FK on the table bindManyToOne( - cascadeStrategy, joinColumns, optional, ignoreNotFound, cascadeOnDelete, + cascadeStrategy, + joinColumns, + optional, + notFoundAction, + cascadeOnDelete, targetEntity, - propertyHolder, inferredData, true, isIdentifierMapper, inSecondPass, - propertyBinder, context + propertyHolder, + inferredData, + true, + isIdentifierMapper, + inSecondPass, + propertyBinder, + context ); } } @@ -3462,10 +3495,20 @@ public static void bindForeignKeyNameAndDefinition( JoinColumns joinColumns, MetadataBuildingContext context) { final boolean noConstraintByDefault = context.getBuildingOptions().isNoConstraintByDefault(); - if ( ( joinColumn != null && ( joinColumn.foreignKey().value() == ConstraintMode.NO_CONSTRAINT - || joinColumn.foreignKey().value() == ConstraintMode.PROVIDER_DEFAULT && noConstraintByDefault ) ) - || ( joinColumns != null && ( joinColumns.foreignKey().value() == ConstraintMode.NO_CONSTRAINT - || joinColumns.foreignKey().value() == ConstraintMode.PROVIDER_DEFAULT && noConstraintByDefault ) ) ) { + + final NotFound notFoundAnn= property.getAnnotation( NotFound.class ); + if ( notFoundAnn != null ) { + // supersedes all others + value.setForeignKeyName( "none" ); + } + else if ( joinColumn != null && ( + joinColumn.foreignKey().value() == ConstraintMode.NO_CONSTRAINT + || ( joinColumn.foreignKey().value() == ConstraintMode.PROVIDER_DEFAULT && noConstraintByDefault ) ) ) { + value.setForeignKeyName( "none" ); + } + else if ( joinColumns != null && ( + joinColumns.foreignKey().value() == ConstraintMode.NO_CONSTRAINT + || ( joinColumns.foreignKey().value() == ConstraintMode.PROVIDER_DEFAULT && noConstraintByDefault ) ) ) { value.setForeignKeyName( "none" ); } else { @@ -3624,12 +3667,12 @@ private static boolean hasAnnotationsOnIdClass(XClass idClass) { return false; } - private static void matchIgnoreNotFoundWithFetchType( + private static void checkFetchModeAgainstNotFound( String entity, String association, - boolean ignoreNotFound, + boolean hasNotFound, FetchType fetchType) { - if ( ignoreNotFound && fetchType == FetchType.LAZY ) { + if ( hasNotFound && fetchType == FetchType.LAZY ) { LOG.ignoreNotFoundWithFetchTypeLazy( entity, association ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/CollectionSecondPass.java b/hibernate-core/src/main/java/org/hibernate/cfg/CollectionSecondPass.java index 24256e2e37a0..4f4108fa9932 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/CollectionSecondPass.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/CollectionSecondPass.java @@ -16,6 +16,7 @@ import org.hibernate.mapping.Collection; import org.hibernate.mapping.IndexedCollection; import org.hibernate.mapping.OneToMany; +import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Selectable; import org.hibernate.mapping.Value; @@ -68,8 +69,7 @@ public void doSecondPass(java.util.Map persistentClasses) } } - abstract public void secondPass(java.util.Map persistentClasses, java.util.Map inheritedMetas) - throws MappingException; + abstract public void secondPass(java.util.Map persistentClasses, java.util.Map inheritedMetas); private static String columns(Value val) { StringBuilder columns = new StringBuilder(); diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/Ejb3JoinColumn.java b/hibernate-core/src/main/java/org/hibernate/cfg/Ejb3JoinColumn.java index 77be05530038..f9d7107fffea 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/Ejb3JoinColumn.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/Ejb3JoinColumn.java @@ -253,7 +253,7 @@ private static Ejb3JoinColumn buildJoinColumn( String suffixForDefaultColumnName, MetadataBuildingContext buildingContext) { if ( ann != null ) { - if ( BinderHelper.isEmptyAnnotationValue( mappedBy ) ) { + if ( !BinderHelper.isEmptyOrNullAnnotationValue( mappedBy ) ) { throw new AnnotationException( "Illegal attempt to define a @JoinColumn with a mappedBy association: " + BinderHelper.getRelativePath( propertyHolder, propertyName ) 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 7a376ee894b4..6da83d1da963 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/OneToOneSecondPass.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/OneToOneSecondPass.java @@ -8,14 +8,13 @@ import java.util.Iterator; import java.util.Map; - import javax.persistence.JoinColumn; import javax.persistence.JoinColumns; import org.hibernate.AnnotationException; -import org.hibernate.FetchMode; import org.hibernate.MappingException; import org.hibernate.annotations.LazyGroup; +import org.hibernate.annotations.NotFoundAction; import org.hibernate.annotations.common.reflection.XClass; import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.cfg.annotations.PropertyBinder; @@ -41,7 +40,7 @@ public class OneToOneSecondPass implements SecondPass { private String ownerEntity; private String ownerProperty; private PropertyHolder propertyHolder; - private boolean ignoreNotFound; + private NotFoundAction notFoundAction; private PropertyData inferredData; private XClass targetEntity; private boolean cascadeOnDelete; @@ -57,7 +56,7 @@ public OneToOneSecondPass( PropertyHolder propertyHolder, PropertyData inferredData, XClass targetEntity, - boolean ignoreNotFound, + NotFoundAction notFoundAction, boolean cascadeOnDelete, boolean optional, String cascadeStrategy, @@ -68,7 +67,7 @@ public OneToOneSecondPass( this.mappedBy = mappedBy; this.propertyHolder = propertyHolder; this.buildingContext = buildingContext; - this.ignoreNotFound = ignoreNotFound; + this.notFoundAction = notFoundAction; this.inferredData = inferredData; this.targetEntity = targetEntity; this.cascadeOnDelete = cascadeOnDelete; @@ -109,6 +108,7 @@ public void doSecondPass(Map persistentClasses) throws MappingException { PropertyBinder binder = new PropertyBinder(); binder.setName( propertyName ); + binder.setProperty( inferredData.getProperty() ); binder.setValue( value ); binder.setCascade( cascadeStrategy ); binder.setAccessType( inferredData.getDefaultAccess() ); @@ -191,7 +191,7 @@ else if ( otherSideProperty.getValue() instanceof ManyToOne ) { ); ManyToOne manyToOne = new ManyToOne( buildingContext, mappedByJoin.getTable() ); //FIXME use ignore not found here - manyToOne.setIgnoreNotFound( ignoreNotFound ); + manyToOne.setNotFoundAction( notFoundAction ); manyToOne.setCascadeDeleteEnabled( value.isCascadeDeleteEnabled() ); manyToOne.setFetchMode( value.getFetchMode() ); manyToOne.setLazy( value.isLazy() ); diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/ToOneFkSecondPass.java b/hibernate-core/src/main/java/org/hibernate/cfg/ToOneFkSecondPass.java index b04c27d16e18..f79c8df5dfe6 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/ToOneFkSecondPass.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/ToOneFkSecondPass.java @@ -106,7 +106,9 @@ public void doSecondPass(java.util.Map persistentClasses) throws MappingExceptio /* * HbmMetadataSourceProcessorImpl does this only when property-ref != null, but IMO, it makes sense event if it is null */ - if ( !manyToOne.isIgnoreNotFound() ) manyToOne.createPropertyRefConstraints( persistentClasses ); + if ( manyToOne.getNotFoundAction() == null ) { + manyToOne.createPropertyRefConstraints( persistentClasses ); + } } else if ( value instanceof OneToOne ) { value.createForeignKey(); 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 9570c1df3d79..23a141065762 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 @@ -50,6 +50,8 @@ import org.hibernate.annotations.LazyGroup; import org.hibernate.annotations.Loader; import org.hibernate.annotations.ManyToAny; +import org.hibernate.annotations.NotFound; +import org.hibernate.annotations.NotFoundAction; import org.hibernate.annotations.OnDelete; import org.hibernate.annotations.OnDeleteAction; import org.hibernate.annotations.OptimisticLock; @@ -156,7 +158,7 @@ public abstract class CollectionBinder { private Ejb3Column[] elementColumns; private boolean isEmbedded; private XProperty property; - private boolean ignoreNotFound; + private NotFoundAction notFoundAction; private TableBinder tableBinder; private Ejb3Column[] mapKeyColumns; private Ejb3JoinColumn[] mapKeyManyToManyColumns; @@ -572,7 +574,7 @@ public void bind() { isEmbedded, property, collectionType, - ignoreNotFound, + notFoundAction, oneToMany, tableBinder, buildingContext @@ -714,6 +716,8 @@ private void defineFetchingStrategy() { ManyToMany manyToMany = property.getAnnotation( ManyToMany.class ); ElementCollection elementCollection = property.getAnnotation( ElementCollection.class ); ManyToAny manyToAny = property.getAnnotation( ManyToAny.class ); + NotFound notFound = property.getAnnotation( NotFound.class ); + FetchType fetchType; if ( oneToMany != null ) { fetchType = oneToMany.fetch(); @@ -732,34 +736,57 @@ else if ( manyToAny != null ) { "Define fetch strategy on a property not annotated with @ManyToOne nor @OneToMany nor @CollectionOfElements" ); } - if ( lazy != null ) { - collection.setLazy( !( lazy.value() == LazyCollectionOption.FALSE ) ); - collection.setExtraLazy( lazy.value() == LazyCollectionOption.EXTRA ); + + if ( notFound != null ) { + collection.setLazy( false ); + + if ( lazy != null ) { + collection.setExtraLazy( lazy.value() == LazyCollectionOption.EXTRA ); + } + + if ( fetch != null ) { + if ( fetch.value() != null ) { + collection.setFetchMode( fetch.value().getHibernateFetchMode() ); + if ( fetch.value() == org.hibernate.annotations.FetchMode.SUBSELECT ) { + collection.setSubselectLoadable( true ); + collection.getOwner().setSubselectLoadableCollections( true ); + } + } + } + else { + collection.setFetchMode( AnnotationBinder.getFetchMode( fetchType ) ); + } } else { - collection.setLazy( fetchType == FetchType.LAZY ); - collection.setExtraLazy( false ); - } - if ( fetch != null ) { - if ( fetch.value() == org.hibernate.annotations.FetchMode.JOIN ) { - collection.setFetchMode( FetchMode.JOIN ); - collection.setLazy( false ); + if ( lazy != null ) { + collection.setLazy( !( lazy.value() == LazyCollectionOption.FALSE ) ); + collection.setExtraLazy( lazy.value() == LazyCollectionOption.EXTRA ); } - else if ( fetch.value() == org.hibernate.annotations.FetchMode.SELECT ) { - collection.setFetchMode( FetchMode.SELECT ); + else { + collection.setLazy( fetchType == FetchType.LAZY ); + collection.setExtraLazy( false ); } - else if ( fetch.value() == org.hibernate.annotations.FetchMode.SUBSELECT ) { - collection.setFetchMode( FetchMode.SELECT ); - collection.setSubselectLoadable( true ); - collection.getOwner().setSubselectLoadableCollections( true ); + if ( fetch != null ) { + if ( fetch.value() == org.hibernate.annotations.FetchMode.JOIN ) { + collection.setFetchMode( FetchMode.JOIN ); + collection.setLazy( false ); + } + else if ( fetch.value() == org.hibernate.annotations.FetchMode.SELECT ) { + collection.setFetchMode( FetchMode.SELECT ); + } + else if ( fetch.value() == org.hibernate.annotations.FetchMode.SUBSELECT ) { + collection.setFetchMode( FetchMode.SELECT ); + collection.setSubselectLoadable( true ); + collection.getOwner().setSubselectLoadableCollections( true ); + } + else { + throw new AssertionFailure( "Unknown FetchMode: " + fetch.value() ); + } } else { - throw new AssertionFailure( "Unknown FetchMode: " + fetch.value() ); + collection.setFetchMode( AnnotationBinder.getFetchMode( fetchType ) ); } } - else { - collection.setFetchMode( AnnotationBinder.getFetchMode( fetchType ) ); - } } private XClass getCollectionType() { @@ -788,14 +815,14 @@ public SecondPass getSecondPass( final boolean isEmbedded, final XProperty property, final XClass collType, - final boolean ignoreNotFound, + final NotFoundAction notFoundAction, final boolean unique, final TableBinder assocTableBinder, final MetadataBuildingContext buildingContext) { return new CollectionSecondPass( buildingContext, collection ) { @SuppressWarnings("rawtypes") @Override - public void secondPass(Map persistentClasses, Map inheritedMetas) throws MappingException { + public void secondPass(Map persistentClasses, Map inheritedMetas) { bindStarToManySecondPass( persistentClasses, collType, @@ -807,7 +834,7 @@ public void secondPass(Map persistentClasses, Map inheritedMetas) throws Mapping property, unique, assocTableBinder, - ignoreNotFound, + notFoundAction, buildingContext ); } @@ -828,7 +855,7 @@ protected boolean bindStarToManySecondPass( XProperty property, boolean unique, TableBinder associationTableBinder, - boolean ignoreNotFound, + NotFoundAction notFoundAction, MetadataBuildingContext buildingContext) { PersistentClass persistentClass = persistentClasses.get( collType.getName() ); boolean reversePropertyInJoin = false; @@ -863,7 +890,7 @@ protected boolean bindStarToManySecondPass( fkJoinColumns, collType, cascadeDeleteEnabled, - ignoreNotFound, + notFoundAction, buildingContext, inheritanceStatePerClass ); @@ -878,7 +905,7 @@ protected boolean bindStarToManySecondPass( inverseColumns, elementColumns, isEmbedded, collType, - ignoreNotFound, unique, + notFoundAction, unique, cascadeDeleteEnabled, associationTableBinder, property, @@ -895,7 +922,7 @@ protected void bindOneToManySecondPass( Ejb3JoinColumn[] fkJoinColumns, XClass collectionType, boolean cascadeDeleteEnabled, - boolean ignoreNotFound, + NotFoundAction notFoundAction, MetadataBuildingContext buildingContext, Map inheritanceStatePerClass) { @@ -910,7 +937,7 @@ protected void bindOneToManySecondPass( org.hibernate.mapping.OneToMany oneToMany = new org.hibernate.mapping.OneToMany( buildingContext, collection.getOwner() ); collection.setElement( oneToMany ); oneToMany.setReferencedEntityName( collectionType.getName() ); - oneToMany.setIgnoreNotFound( ignoreNotFound ); + oneToMany.setNotFoundAction( notFoundAction ); String assocClass = oneToMany.getReferencedEntityName(); PersistentClass associatedClass = persistentClasses.get( assocClass ); @@ -1314,7 +1341,7 @@ private void bindManyToManySecondPass( Ejb3Column[] elementColumns, boolean isEmbedded, XClass collType, - boolean ignoreNotFound, boolean unique, + NotFoundAction notFoundAction, boolean unique, boolean cascadeDeleteEnabled, TableBinder associationTableBinder, XProperty property, @@ -1457,7 +1484,7 @@ else if ( anyAnn != null ) { //make the second join non lazy element.setFetchMode( FetchMode.JOIN ); element.setLazy( false ); - element.setIgnoreNotFound( ignoreNotFound ); + element.setNotFoundAction( notFoundAction ); // as per 11.1.38 of JPA 2.0 spec, default to primary key if no column is specified by @OrderBy. if ( hqlOrderBy != null ) { collValue.setManyToManyOrdering( @@ -1824,8 +1851,21 @@ public void setProperty(XProperty property) { this.property = property; } + public NotFoundAction getNotFoundAction() { + return notFoundAction; + } + + public void setNotFoundAction(NotFoundAction notFoundAction) { + this.notFoundAction = notFoundAction; + } + public void setIgnoreNotFound(boolean ignoreNotFound) { - this.ignoreNotFound = ignoreNotFound; + if ( ignoreNotFound ) { + setNotFoundAction( NotFoundAction.IGNORE ); + } + else { + setNotFoundAction( null ); + } } public void setMapKeyColumns(Ejb3Column[] mapKeyColumns) { diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/IdBagBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/IdBagBinder.java index 34ef384638e7..9bfb2b62f44d 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/IdBagBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/IdBagBinder.java @@ -13,6 +13,7 @@ import org.hibernate.AnnotationException; import org.hibernate.annotations.CollectionId; +import org.hibernate.annotations.NotFoundAction; import org.hibernate.annotations.Type; import org.hibernate.annotations.common.reflection.XClass; import org.hibernate.annotations.common.reflection.XProperty; @@ -52,11 +53,11 @@ protected boolean bindStarToManySecondPass( XProperty property, boolean unique, TableBinder associationTableBinder, - boolean ignoreNotFound, + NotFoundAction notFoundAction, MetadataBuildingContext buildingContext) { boolean result = super.bindStarToManySecondPass( persistentClasses, collType, fkJoinColumns, keyColumns, inverseColumns, elementColumns, isEmbedded, - property, unique, associationTableBinder, ignoreNotFound, getBuildingContext() + property, unique, associationTableBinder, notFoundAction, getBuildingContext() ); CollectionId collectionIdAnn = property.getAnnotation( CollectionId.class ); if ( collectionIdAnn != null ) { diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/ListBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/ListBinder.java index 69aa6c597699..5f09945873d8 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/ListBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/ListBinder.java @@ -10,6 +10,7 @@ import org.hibernate.AnnotationException; import org.hibernate.MappingException; +import org.hibernate.annotations.NotFoundAction; import org.hibernate.annotations.OrderBy; import org.hibernate.annotations.Sort; import org.hibernate.annotations.common.reflection.XClass; @@ -76,14 +77,13 @@ public SecondPass getSecondPass( final boolean isEmbedded, final XProperty property, final XClass collType, - final boolean ignoreNotFound, + final NotFoundAction notFoundAction, final boolean unique, final TableBinder assocTableBinder, final MetadataBuildingContext buildingContext) { return new CollectionSecondPass( getBuildingContext(), ListBinder.this.collection ) { @Override - public void secondPass(Map persistentClasses, Map inheritedMetas) - throws MappingException { + public void secondPass(Map persistentClasses, Map inheritedMetas) { bindStarToManySecondPass( persistentClasses, collType, @@ -95,7 +95,7 @@ public void secondPass(Map persistentClasses, Map inheritedMetas) property, unique, assocTableBinder, - ignoreNotFound, + notFoundAction, buildingContext ); bindIndex( buildingContext ); diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/MapBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/MapBinder.java index 4a4f83fe1c97..2d9920dacbf7 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/MapBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/MapBinder.java @@ -23,13 +23,17 @@ import org.hibernate.AssertionFailure; import org.hibernate.FetchMode; import org.hibernate.MappingException; +import org.hibernate.annotations.NotFoundAction; import org.hibernate.annotations.common.reflection.XClass; import org.hibernate.annotations.common.reflection.XProperty; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; +import org.hibernate.boot.model.relational.internal.SqlStringGenerationContextImpl; import org.hibernate.boot.spi.BootstrapContext; import org.hibernate.boot.spi.MetadataBuildingContext; 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; @@ -41,6 +45,8 @@ import org.hibernate.cfg.PropertyPreloadedData; import org.hibernate.cfg.SecondPass; import org.hibernate.dialect.HSQLDialect; +import org.hibernate.engine.config.spi.ConfigurationService; +import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.internal.util.StringHelper; import org.hibernate.mapping.Collection; import org.hibernate.mapping.Column; @@ -56,6 +62,7 @@ import org.hibernate.mapping.SimpleValue; import org.hibernate.mapping.ToOne; import org.hibernate.mapping.Value; +import org.hibernate.service.ServiceRegistry; import org.hibernate.sql.Template; /** @@ -87,16 +94,15 @@ public SecondPass getSecondPass( final boolean isEmbedded, final XProperty property, final XClass collType, - final boolean ignoreNotFound, + final NotFoundAction notFoundAction, final boolean unique, final TableBinder assocTableBinder, final MetadataBuildingContext buildingContext) { return new CollectionSecondPass( buildingContext, MapBinder.this.collection ) { - public void secondPass(Map persistentClasses, Map inheritedMetas) - throws MappingException { + public void secondPass(Map persistentClasses, Map inheritedMetas) { bindStarToManySecondPass( persistentClasses, collType, fkJoinColumns, keyColumns, inverseColumns, elementColumns, - isEmbedded, property, unique, assocTableBinder, ignoreNotFound, buildingContext + isEmbedded, property, unique, assocTableBinder, notFoundAction, buildingContext ); bindKeyFromAssociationTable( collType, persistentClasses, mapKeyPropertyName, property, isEmbedded, buildingContext, @@ -412,6 +418,14 @@ protected Value createFormulatedValue( MetadataBuildingContext buildingContext) { Value element = collection.getElement(); String fromAndWhere = null; + final ServiceRegistry serviceRegistry = buildingContext.getBootstrapContext().getServiceRegistry(); + final ConfigurationService configurationService = serviceRegistry.getService( ConfigurationService.class); + final SqlStringGenerationContext generationContext = SqlStringGenerationContextImpl.fromExplicit( + serviceRegistry.getService( JdbcServices.class).getJdbcEnvironment(), + buildingContext.getMetadataCollector().getDatabase(), + configurationService.getSetting(AvailableSettings.DEFAULT_CATALOG, String.class, null), + configurationService.getSetting( AvailableSettings.DEFAULT_SCHEMA, String.class, null) + ); if ( !( element instanceof OneToMany ) ) { String referencedPropertyName = null; if ( element instanceof ToOne ) { @@ -435,7 +449,7 @@ else if ( element instanceof DependantValue ) { referencedEntityColumns = referencedProperty.getColumnIterator(); } fromAndWhere = getFromAndWhereFormula( - associatedClass.getTable().getQualifiedTableName().toString(), + generationContext.format(associatedClass.getTable().getQualifiedTableName()), element.getColumnIterator(), referencedEntityColumns ); @@ -444,9 +458,7 @@ else if ( element instanceof DependantValue ) { // HHH-11005 - only if we are OneToMany and location of map key property is at a different level, need to add a select if ( !associatedClass.equals( targetPropertyPersistentClass ) ) { fromAndWhere = getFromAndWhereFormula( - targetPropertyPersistentClass.getTable() - .getQualifiedTableName() - .toString(), + generationContext.format(targetPropertyPersistentClass.getTable().getQualifiedTableName()), element.getColumnIterator(), associatedClass.getIdentifier().getColumnIterator() ); diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/PropertyBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/PropertyBinder.java index d3986b61cc92..7cb33fd33347 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/PropertyBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/PropertyBinder.java @@ -273,7 +273,9 @@ public Property makeProperty() { prop.setPropertyAccessorName( accessType.getType() ); if ( property != null ) { - prop.setValueGenerationStrategy( determineValueGenerationStrategy( property ) ); + if ( entityBinder != null ) { + prop.setValueGenerationStrategy( determineValueGenerationStrategy( property ) ); + } if ( property.isAnnotationPresent( AttributeAccessor.class ) ) { final AttributeAccessor accessor = property.getAnnotation( AttributeAccessor.class ); diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/internal/JPAXMLOverriddenAnnotationReader.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/internal/JPAXMLOverriddenAnnotationReader.java index a020b8607da1..7f4553145901 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/internal/JPAXMLOverriddenAnnotationReader.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/internal/JPAXMLOverriddenAnnotationReader.java @@ -179,7 +179,6 @@ import org.hibernate.boot.jaxb.mapping.spi.JaxbStoredProcedureParameter; import org.hibernate.boot.jaxb.mapping.spi.JaxbTable; import org.hibernate.boot.jaxb.mapping.spi.JaxbTableGenerator; -import org.hibernate.boot.jaxb.mapping.spi.JaxbTransient; import org.hibernate.boot.jaxb.mapping.spi.JaxbUniqueConstraint; import org.hibernate.boot.jaxb.mapping.spi.JaxbVersion; import org.hibernate.boot.jaxb.mapping.spi.LifecycleCallbackContainer; diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/internal/JPAXMLOverriddenMetadataProvider.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/internal/JPAXMLOverriddenMetadataProvider.java index afd35add4866..04733894cc78 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/internal/JPAXMLOverriddenMetadataProvider.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/internal/JPAXMLOverriddenMetadataProvider.java @@ -36,7 +36,7 @@ * @author Emmanuel Bernard */ @SuppressWarnings("unchecked") -public final class JPAXMLOverriddenMetadataProvider implements MetadataProvider { +public class JPAXMLOverriddenMetadataProvider implements MetadataProvider { private static final MetadataProvider STATELESS_BASE_DELEGATE = new JavaMetadataProvider(); diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/internal/PropertyMappingElementCollector.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/internal/PropertyMappingElementCollector.java index 97bf6b89ea23..791fdc20371c 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/internal/PropertyMappingElementCollector.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/internal/PropertyMappingElementCollector.java @@ -43,9 +43,9 @@ *

    • Only create lists if we actually have elements (most lists should be empty in most cases)
    • *
    */ -final class PropertyMappingElementCollector { - static final Function PERSISTENT_ATTRIBUTE_NAME = PersistentAttribute::getName; - static final Function JAXB_TRANSIENT_NAME = JaxbTransient::getName; +public final class PropertyMappingElementCollector { + public static final Function PERSISTENT_ATTRIBUTE_NAME = PersistentAttribute::getName; + public static final Function JAXB_TRANSIENT_NAME = JaxbTransient::getName; static final Function LIFECYCLE_CALLBACK_NAME = LifecycleCallback::getMethodName; private final String propertyName; @@ -70,7 +70,7 @@ final class PropertyMappingElementCollector { private List postUpdate; private List postLoad; - PropertyMappingElementCollector(String propertyName) { + public PropertyMappingElementCollector(String propertyName) { this.propertyName = propertyName; } diff --git a/hibernate-core/src/main/java/org/hibernate/collection/spi/LazyInitializable.java b/hibernate-core/src/main/java/org/hibernate/collection/spi/LazyInitializable.java new file mode 100644 index 000000000000..38c8345181ec --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/collection/spi/LazyInitializable.java @@ -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 . + */ +package org.hibernate.collection.spi; + +import org.hibernate.Incubating; + +/** + * Hibernate "wraps" a java collection in an instance of PersistentCollection. Envers uses custom collection + * wrappers (ListProxy, SetProxy, etc). All of them need to extend LazyInitializable, so the + * Hibernate.isInitialized method can check if the collection is initialized or not. + * + * @author Fabricio Gregorio + */ +@Incubating +public interface LazyInitializable { + + /** + * Is this instance initialized? + * + * @return Was this collection initialized? Or is its data still not (fully) loaded? + */ + boolean wasInitialized(); + + /** + * To be called internally by the session, forcing immediate initialization. + */ + void forceInitialization(); + +} 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 0a98641c38b7..8fa8817d0d60 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 @@ -45,7 +45,7 @@ * * @author Gavin King */ -public interface PersistentCollection { +public interface PersistentCollection extends LazyInitializable { /** * Get the owning entity. Note that the owner is only * set during the flush cycle, and when a new collection @@ -271,11 +271,6 @@ Object readFrom(ResultSet rs, CollectionPersister role, CollectionAliases descri */ Serializable getSnapshot(CollectionPersister persister); - /** - * To be called internally by the session, forcing immediate initialization. - */ - void forceInitialization(); - /** * Does the given element/entry exist in the collection? * @@ -335,13 +330,6 @@ Object readFrom(ResultSet rs, CollectionPersister role, CollectionAliases descri */ boolean isWrapper(Object collection); - /** - * Is this instance initialized? - * - * @return Was this collection initialized? Or is its data still not (fully) loaded? - */ - boolean wasInitialized(); - /** * Does this instance have any "queued" operations? * 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 3dbebe2e9db4..d5b672687419 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 000000000000..8fc343e69d66 --- /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 000000000000..fa81e944f369 --- /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 000000000000..6df5fb1cc749 --- /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/LikeExpression.java b/hibernate-core/src/main/java/org/hibernate/criterion/LikeExpression.java index e266a70d784d..b846cacc80d6 100644 --- a/hibernate-core/src/main/java/org/hibernate/criterion/LikeExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/criterion/LikeExpression.java @@ -78,7 +78,7 @@ public String toSqlString(Criteria criteria,CriteriaQuery criteriaQuery) { @Override public TypedValue[] getTypedValues(Criteria criteria, CriteriaQuery criteriaQuery) { - final String matchValue = ignoreCase ? value.toString().toLowerCase(Locale.ROOT) : value.toString(); + final String matchValue = ignoreCase ? value.toString().toLowerCase() : value.toString(); return new TypedValue[] { criteriaQuery.getTypedValue( criteria, propertyName, matchValue ) }; } 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 e2826eb7930d..dbb310622d75 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 548ed9e09b51..64993d4ad3c9 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/criterion/SimpleExpression.java b/hibernate-core/src/main/java/org/hibernate/criterion/SimpleExpression.java index 5d377d78de7b..caee68875543 100644 --- a/hibernate-core/src/main/java/org/hibernate/criterion/SimpleExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/criterion/SimpleExpression.java @@ -39,7 +39,7 @@ protected SimpleExpression(String propertyName, Object value, String op, boolean this.op = op; } - protected final String getOp() { + public final String getOp() { return op; } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java index b69f036ff332..5b1dff81e203 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java @@ -20,6 +20,7 @@ import org.hibernate.dialect.function.StandardSQLFunction; import org.hibernate.dialect.function.VarArgsSQLFunction; import org.hibernate.dialect.hint.IndexQueryHintHandler; +import org.hibernate.dialect.identity.H2FinalTableIdentityColumnSupport; import org.hibernate.dialect.identity.H2IdentityColumnSupport; import org.hibernate.dialect.identity.IdentityColumnSupport; import org.hibernate.dialect.pagination.AbstractLimitHandler; @@ -505,7 +506,7 @@ public boolean supportsIfExistsBeforeTableName() { @Override public IdentityColumnSupport getIdentityColumnSupport() { - return new H2IdentityColumnSupport(); + return isVersion2 ? H2FinalTableIdentityColumnSupport.INSTANCE : H2IdentityColumnSupport.INSTANCE; } @Override 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 d6e7186b5f65..e28b5371a035 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java @@ -50,6 +50,12 @@ public String getNullColumnString() { return " null"; } + @Override + public boolean canCreateSchema() { + // As far as I can tell, it does not + return false; + } + @Override public String getCurrentSchemaCommand() { return "select db_name()"; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/SQLFunctionRegistry.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/SQLFunctionRegistry.java index 48d413fa6b71..3c8d95bd7a68 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/SQLFunctionRegistry.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/SQLFunctionRegistry.java @@ -39,9 +39,12 @@ public SQLFunctionRegistry(Dialect dialect, Map userFunctio * * @param functionName The name of the function to locate * - * @return The located function, maye return {@code null} + * @return The located function, may return {@code null} */ - public SQLFunction findSQLFunction(String functionName) { + public SQLFunction findSQLFunction(final String functionName) { + if ( functionName == null ) { + return null; + } return functionMap.get( functionName ); } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/identity/H2FinalTableIdentityColumnSupport.java b/hibernate-core/src/main/java/org/hibernate/dialect/identity/H2FinalTableIdentityColumnSupport.java new file mode 100644 index 000000000000..3f93ae73868c --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/identity/H2FinalTableIdentityColumnSupport.java @@ -0,0 +1,29 @@ +/* + * 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.dialect.identity; + +/** + * Identity column support for H2 2+ versions + * @author Jan Schatteman + */ +public class H2FinalTableIdentityColumnSupport extends H2IdentityColumnSupport { + + public static final H2FinalTableIdentityColumnSupport INSTANCE = new H2FinalTableIdentityColumnSupport(); + + private H2FinalTableIdentityColumnSupport() { + } + + @Override + public boolean supportsInsertSelectIdentity() { + return true; + } + + @Override + public String appendIdentitySelectToInsert(String identityColumnName, String insertString) { + return "select " + identityColumnName + " from final table ( " + insertString + " )"; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/identity/H2IdentityColumnSupport.java b/hibernate-core/src/main/java/org/hibernate/dialect/identity/H2IdentityColumnSupport.java index 8ea8827a6c1f..35bcf5d32b59 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/identity/H2IdentityColumnSupport.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/identity/H2IdentityColumnSupport.java @@ -10,6 +10,12 @@ * @author Andrea Boriero */ public class H2IdentityColumnSupport extends IdentityColumnSupportImpl { + + public static final H2IdentityColumnSupport INSTANCE = new H2IdentityColumnSupport(); + + protected H2IdentityColumnSupport() { + } + @Override public boolean supportsIdentityColumns() { return true; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/identity/IdentityColumnSupport.java b/hibernate-core/src/main/java/org/hibernate/dialect/identity/IdentityColumnSupport.java index e63592fbe556..e000d8357f32 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/identity/IdentityColumnSupport.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/identity/IdentityColumnSupport.java @@ -56,6 +56,23 @@ public interface IdentityColumnSupport { */ String appendIdentitySelectToInsert(String insertString); + /** + * Provided we {@link #supportsInsertSelectIdentity}, then attach the + * "select identity" clause to the insert statement. + *

    + * Note, if {@link #supportsInsertSelectIdentity} == false then + * the insert-string should be returned without modification. + * + * @param identityColumnName The name of the identity column + * @param insertString The insert command + * + * @return The insert command with any necessary identity select + * clause attached. + */ + default String appendIdentitySelectToInsert(String identityColumnName, String insertString) { + return appendIdentitySelectToInsert( insertString ); + } + /** * Get the select command to use to retrieve the last generated IDENTITY * value for a particular table diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/unique/UniqueDelegate.java b/hibernate-core/src/main/java/org/hibernate/dialect/unique/UniqueDelegate.java index 6fdc173af796..a00dff0f7e05 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/unique/UniqueDelegate.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/unique/UniqueDelegate.java @@ -35,6 +35,21 @@ * @author Brett Meyer */ public interface UniqueDelegate { + /** + * Get the fragment that can be used to make a column unique as part of its column definition. + *

    + * This is intended for dialects which do not support unique constraints + * + * @param column The column to which to apply the unique + * @return The fragment (usually "unique"), empty string indicates the uniqueness will be indicated using a + * different approach + * @deprecated Implement {@link #getColumnDefinitionUniquenessFragment(Column, SqlStringGenerationContext)} instead. + */ + @Deprecated + default String getColumnDefinitionUniquenessFragment(Column column) { + throw new IllegalStateException("getColumnDefinitionUniquenessFragment(...) was not implemented!"); + } + /** * Get the fragment that can be used to make a column unique as part of its column definition. *

    @@ -45,7 +60,27 @@ public interface UniqueDelegate { * @return The fragment (usually "unique"), empty string indicates the uniqueness will be indicated using a * different approach */ - public String getColumnDefinitionUniquenessFragment(Column column, SqlStringGenerationContext context); + default String getColumnDefinitionUniquenessFragment(Column column, SqlStringGenerationContext context) { + return getColumnDefinitionUniquenessFragment( column ); + } + + /** + * Get the fragment that can be used to apply unique constraints as part of table creation. The implementation + * should iterate over the {@link org.hibernate.mapping.UniqueKey} instances for the given table (see + * {@link org.hibernate.mapping.Table#getUniqueKeyIterator()} and generate the whole fragment for all + * unique keys + *

    + * Intended for Dialects which support unique constraint definitions, but just not in separate ALTER statements. + * + * @param table The table for which to generate the unique constraints fragment + * @return The fragment, typically in the form {@code ", unique(col1, col2), unique( col20)"}. NOTE: The leading + * comma is important! + * @deprecated Implement {@link #getTableCreationUniqueConstraintsFragment(Table, SqlStringGenerationContext)} instead. + */ + @Deprecated + default String getTableCreationUniqueConstraintsFragment(Table table) { + throw new IllegalStateException("getTableCreationUniqueConstraintsFragment(...) was not implemented!"); + } /** * Get the fragment that can be used to apply unique constraints as part of table creation. The implementation @@ -60,7 +95,22 @@ public interface UniqueDelegate { * @return The fragment, typically in the form {@code ", unique(col1, col2), unique( col20)"}. NOTE: The leading * comma is important! */ - public String getTableCreationUniqueConstraintsFragment(Table table, SqlStringGenerationContext context); + default String getTableCreationUniqueConstraintsFragment(Table table, SqlStringGenerationContext context) { + return getTableCreationUniqueConstraintsFragment( table ); + } + + /** + * Get the SQL ALTER TABLE command to be used to create the given UniqueKey. + * + * @param uniqueKey The UniqueKey instance. Contains all information about the columns + * @param metadata Access to the bootstrap mapping information + * @return The ALTER TABLE command + * @deprecated Implement {@link #getAlterTableToAddUniqueKeyCommand(UniqueKey, Metadata, SqlStringGenerationContext)} instead. + */ + @Deprecated + default String getAlterTableToAddUniqueKeyCommand(UniqueKey uniqueKey, Metadata metadata) { + throw new IllegalStateException("getAlterTableToAddUniqueKeyCommand(...) was not implemented!"); + } /** * Get the SQL ALTER TABLE command to be used to create the given UniqueKey. @@ -70,8 +120,23 @@ public interface UniqueDelegate { * @param context A context for SQL string generation * @return The ALTER TABLE command */ - public String getAlterTableToAddUniqueKeyCommand(UniqueKey uniqueKey, Metadata metadata, - SqlStringGenerationContext context); + default String getAlterTableToAddUniqueKeyCommand(UniqueKey uniqueKey, Metadata metadata, + SqlStringGenerationContext context) { + return getAlterTableToAddUniqueKeyCommand( uniqueKey, metadata ); + } + + /** + * Get the SQL ALTER TABLE command to be used to drop the given UniqueKey. + * + * @param uniqueKey The UniqueKey instance. Contains all information about the columns + * @param metadata Access to the bootstrap mapping information + * @return The ALTER TABLE command + * @deprecated Implement {@link #getAlterTableToDropUniqueKeyCommand(UniqueKey, Metadata, SqlStringGenerationContext)} instead. + */ + @Deprecated + default String getAlterTableToDropUniqueKeyCommand(UniqueKey uniqueKey, Metadata metadata) { + throw new IllegalStateException("getAlterTableToDropUniqueKeyCommand(...) was not implemented!"); + } /** * Get the SQL ALTER TABLE command to be used to drop the given UniqueKey. @@ -81,7 +146,9 @@ public String getAlterTableToAddUniqueKeyCommand(UniqueKey uniqueKey, Metadata m * @param context A context for SQL string generation * @return The ALTER TABLE command */ - public String getAlterTableToDropUniqueKeyCommand(UniqueKey uniqueKey, Metadata metadata, - SqlStringGenerationContext context); + default String getAlterTableToDropUniqueKeyCommand(UniqueKey uniqueKey, Metadata metadata, + SqlStringGenerationContext context) { + return getAlterTableToDropUniqueKeyCommand( uniqueKey, metadata ); + } } 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 cfa74558cead..abb003c63290 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 @@ -22,6 +22,7 @@ import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityEntryExtraState; import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.Managed; import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.PersistentAttributeInterceptable; import org.hibernate.engine.spi.PersistentAttributeInterceptor; @@ -279,9 +280,7 @@ public void postUpdate(Object entity, Object[] updatedState, Object nextVersion) getPersister().setPropertyValue( entity, getPersister().getVersionProperty(), nextVersion ); } - if( entity instanceof SelfDirtinessTracker ) { - ( (SelfDirtinessTracker) entity ).$$_hibernate_clearDirtyAttributes(); - } + ManagedTypeHelper.processIfSelfDirtinessTracker( entity, AbstractEntityEntry::clearDirtyAttributes ); getPersistenceContext().getSession() .getFactory() @@ -289,6 +288,10 @@ public void postUpdate(Object entity, Object[] updatedState, Object nextVersion) .resetDirty( entity, getPersister(), (Session) getPersistenceContext().getSession() ); } + private static void clearDirtyAttributes(final SelfDirtinessTracker entity) { + entity.$$_hibernate_clearDirtyAttributes(); + } + @Override public void postDelete() { setCompressedValue( EnumState.PREVIOUS_STATUS, getStatus() ); @@ -345,10 +348,10 @@ public boolean requiresDirtyCheck(Object entity) { @SuppressWarnings( {"SimplifiableIfStatement"}) private boolean isUnequivocallyNonDirty(Object entity) { - if ( entity instanceof SelfDirtinessTracker ) { + if ( ManagedTypeHelper.isSelfDirtinessTracker( entity ) ) { boolean uninitializedProxy = false; - if ( entity instanceof PersistentAttributeInterceptable ) { - final PersistentAttributeInterceptable interceptable = (PersistentAttributeInterceptable) entity; + if ( ManagedTypeHelper.isPersistentAttributeInterceptable( entity ) ) { + final PersistentAttributeInterceptable interceptable = ManagedTypeHelper.asPersistentAttributeInterceptable( entity ); final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor(); if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { EnhancementAsProxyLazinessInterceptor enhancementAsProxyLazinessInterceptor = (EnhancementAsProxyLazinessInterceptor) interceptor; @@ -365,11 +368,11 @@ else if ( entity instanceof HibernateProxy ) { // we never have to check an uninitialized proxy return uninitializedProxy || !persister.hasCollections() && !persister.hasMutableProperties() - && !( (SelfDirtinessTracker) entity ).$$_hibernate_hasDirtyAttributes(); + && !ManagedTypeHelper.asSelfDirtinessTracker( entity ).$$_hibernate_hasDirtyAttributes(); } - if ( entity instanceof PersistentAttributeInterceptable ) { - final PersistentAttributeInterceptable interceptable = (PersistentAttributeInterceptable) entity; + if ( ManagedTypeHelper.isPersistentAttributeInterceptable( entity ) ) { + final PersistentAttributeInterceptable interceptable = ManagedTypeHelper.asPersistentAttributeInterceptable( entity ); final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor(); if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { // we never have to check an uninitialized proxy diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/EntityEntryContext.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/EntityEntryContext.java index 97f20b183509..b67e2292695e 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/EntityEntryContext.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/EntityEntryContext.java @@ -9,6 +9,7 @@ import org.hibernate.HibernateException; import org.hibernate.LockMode; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; +import org.hibernate.engine.internal.ManagedTypeHelper; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.ManagedEntity; import org.hibernate.engine.spi.PersistenceContext; @@ -91,21 +92,22 @@ public void addEntityEntry(Object entity, EntityEntry entityEntry) { ManagedEntity managedEntity = getAssociatedManagedEntity( entity ); final boolean alreadyAssociated = managedEntity != null; if ( !alreadyAssociated ) { - if ( ManagedEntity.class.isInstance( entity ) ) { + if ( ManagedTypeHelper.isManagedEntity( entity ) ) { + final ManagedEntity managed = ManagedTypeHelper.asManagedEntity( entity ); if ( entityEntry.getPersister().isMutable() ) { - managedEntity = (ManagedEntity) entity; + managedEntity = managed; // We know that managedEntity is not associated with the same PersistenceContext. // Check if managedEntity is associated with a different PersistenceContext. checkNotAssociatedWithOtherPersistenceContextIfMutable( managedEntity ); } else { // Create a holder for PersistenceContext-related data. - managedEntity = new ImmutableManagedEntityHolder( (ManagedEntity) entity ); + managedEntity = new ImmutableManagedEntityHolder( managed ); if ( immutableManagedEntityXref == null ) { immutableManagedEntityXref = new IdentityHashMap(); } immutableManagedEntityXref.put( - (ManagedEntity) entity, + managed, (ImmutableManagedEntityHolder) managedEntity ); } @@ -150,8 +152,8 @@ public void addEntityEntry(Object entity, EntityEntry entityEntry) { } private ManagedEntity getAssociatedManagedEntity(Object entity) { - if ( ManagedEntity.class.isInstance( entity ) ) { - final ManagedEntity managedEntity = (ManagedEntity) entity; + if ( ManagedTypeHelper.isManagedEntity( entity ) ) { + final ManagedEntity managedEntity = ManagedTypeHelper.asManagedEntity( entity ); if ( managedEntity.$$_hibernate_getEntityEntry() == null ) { // it is not associated return null; @@ -368,7 +370,10 @@ public void downgradeLocks() { ManagedEntity node = head; while ( node != null ) { - node.$$_hibernate_getEntityEntry().setLockMode( LockMode.NONE ); + EntityEntry entityEntry = node.$$_hibernate_getEntityEntry(); + if ( entityEntry != null ) { + entityEntry.setLockMode( LockMode.NONE ); + } node = node.$$_hibernate_getNextManagedEntity(); } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/ManagedTypeHelper.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/ManagedTypeHelper.java new file mode 100644 index 000000000000..284660f56b59 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/ManagedTypeHelper.java @@ -0,0 +1,188 @@ +/* + * 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.engine.internal; + +import org.hibernate.engine.spi.EnhancedEntity; +import org.hibernate.engine.spi.Managed; +import org.hibernate.engine.spi.ManagedEntity; +import org.hibernate.engine.spi.PersistentAttributeInterceptable; +import org.hibernate.engine.spi.SelfDirtinessTracker; + +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +/** + * This is a helper to encapsulate an optimal strategy to execute type checks + * for interfaces which attempts to avoid the performance issues tracked + * as https://bugs.openjdk.org/browse/JDK-8180450 ; + * the problem is complex and best understood by reading the OpenJDK tracker; + * we'll focus on a possible solution here. + *

    + * To avoid polluting the secondary super-type cache, the important aspect is to + * not switch types repeatedly for the same concrete object; using a Java + * agent which was developed for this purpose (https://github.com/franz1981/type-pollution-agent) + * we identified a strong case with Hibernate ORM is triggered when the entities are + * using bytecode enhancement, as they are being checked by a set of interfaces: + * {@see org.hibernate.engine.spi.PersistentAttributeInterceptable} + * {@see org.hibernate.engine.spi.ManagedEntity} + * {@see org.hibernate.engine.spi.SelfDirtinessTracker} + * {@see org.hibernate.engine.spi.Managed} + * With our domain knowledge, we bet on the assumption that either enhancement isn't being + * used at all, OR that when enhancement is being used, there is a strong likelyhood for + * all of these supertypes to be have been injected into the managed objected of the domain + * model (this isn't a certainty as otherwise we'd not have multiple interfaces to separate + * these), but we're working based on the assumption so to at least optimise for what + * we expect being a very common configuration. + * (At this time we won't optimise also embeddables and other corner cases, which will + * need to be looked at separately). + * We therefore introduce a new marker interface {@see EnhancedEntity}, which extends + * all these other contracts, and have the enhancer tool apply it when all other interfaces + * have been applied. + * This then allows to check always and consistently for this type only; as fallback + * path, we perform the "traditional" operation as it would have been before this patch. + * @author Sanne Grinovero + */ +public final class ManagedTypeHelper { + + /** + * @param type + * @return true if and only if the type is assignable to a {@see Managed} type. + */ + public static boolean isManagedType(final Class type) { + return EnhancedEntity.class.isAssignableFrom( type ) || Managed.class.isAssignableFrom( type ); + } + + /** + * @param entity + * @return true if and only if the entity implements {@see Managed} + */ + public static boolean isManaged(final Object entity) { + return entity instanceof EnhancedEntity || entity instanceof Managed; + } + + /** + * @param entity + * @return true if and only if the entity implements {@see ManagedEntity} + */ + public static boolean isManagedEntity(Object entity) { + return entity instanceof EnhancedEntity || entity instanceof ManagedEntity; + } + + /** + * @param type + * @return true if and only if the type is assignable to a {@see PersistentAttributeInterceptable} type. + */ + public static boolean isPersistentAttributeInterceptableType(final Class type) { + return EnhancedEntity.class.isAssignableFrom( type ) || PersistentAttributeInterceptable.class.isAssignableFrom( type ); + } + + /** + * @param entity + * @return true if and only if the entity implements {@see PersistentAttributeInterceptable} + */ + public static boolean isPersistentAttributeInterceptable(final Object entity) { + return entity instanceof EnhancedEntity || entity instanceof PersistentAttributeInterceptable; + } + + /** + * @param entity + * @return true if and only if the entity implements {@see SelfDirtinessTracker} + */ + public static boolean isSelfDirtinessTracker(final Object entity) { + return entity instanceof EnhancedEntity || entity instanceof SelfDirtinessTracker; + } + + /** + * Helper to execute an action on an entity, but exclusively if it's implementing the {@see PersistentAttributeInterceptable} + * interface. Otherwise no action is performed. + * + * @param entity + * @param action The action to be performed; it should take the entity as first parameter, and an additional parameter T as second parameter. + * @param optionalParam a parameter which can be passed to the action + * @param the type of the additional parameter. + */ + public static void processIfPersistentAttributeInterceptable( + final Object entity, + final BiConsumer action, + final T optionalParam) { + if ( entity instanceof EnhancedEntity ) { + EnhancedEntity e = (EnhancedEntity) entity; + action.accept( e, optionalParam ); + } + else if ( entity instanceof PersistentAttributeInterceptable ) { + PersistentAttributeInterceptable e = (PersistentAttributeInterceptable) entity; + action.accept( e, optionalParam ); + } + } + + /** + * If the entity is implementing SelfDirtinessTracker, apply some action to it. + * It is first cast to SelfDirtinessTracker using an optimal strategy. + * If the entity does not implement SelfDirtinessTracker, no operation is performed. + * @param entity + * @param action + */ + public static void processIfSelfDirtinessTracker(final Object entity, final Consumer action) { + if ( entity instanceof EnhancedEntity ) { + EnhancedEntity e = (EnhancedEntity) entity; + action.accept( e ); + } + else if ( entity instanceof SelfDirtinessTracker ) { + SelfDirtinessTracker e = (SelfDirtinessTracker) entity; + action.accept( e ); + } + } + + /** + * Cast the object to PersistentAttributeInterceptable + * (using this is highly preferrable over a direct cast) + * @param entity the entity to cast + * @return the same instance after casting + * @throws ClassCastException if it's not of the right type + */ + public static PersistentAttributeInterceptable asPersistentAttributeInterceptable(final Object entity) { + if ( entity instanceof EnhancedEntity ) { + return (EnhancedEntity) entity; + } + else { + return (PersistentAttributeInterceptable) entity; + } + } + + /** + * Cast the object to ManagedEntity + * (using this is highly preferrable over a direct cast) + * @param entity the entity to cast + * @return the same instance after casting + * @throws ClassCastException if it's not of the right type + */ + public static ManagedEntity asManagedEntity(final Object entity) { + if ( entity instanceof EnhancedEntity ) { + return (EnhancedEntity) entity; + } + else { + return (ManagedEntity) entity; + } + } + + /** + * Cast the object to SelfDirtinessTracker + * (using this is highly preferrable over a direct cast) + * @param entity the entity to cast + * @return the same instance after casting + * @throws ClassCastException if it's not of the right type + */ + public static SelfDirtinessTracker asSelfDirtinessTracker(final Object entity) { + if ( entity instanceof EnhancedEntity ) { + return (EnhancedEntity) entity; + } + else { + return (SelfDirtinessTracker) entity; + } + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/NaturalIdXrefDelegate.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/NaturalIdXrefDelegate.java index 71233928bfb3..ed464b0ab1ee 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/NaturalIdXrefDelegate.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/NaturalIdXrefDelegate.java @@ -87,13 +87,14 @@ public boolean cacheNaturalIdCrossReference(EntityPersister persister, Serializa /** * Handle removing cross reference entries for the given natural-id/pk combo * - * @param persister The persister representing the entity type. - * @param pk The primary key value + * @param persister The persister representing the entity type. + * @param pk The primary key value * @param naturalIdValues The natural id value(s) - * + * @param removeOnNaturalIdCache remove the entry on shared cache too + * * @return The cached values, if any. May be different from incoming values. */ - public Object[] removeNaturalIdCrossReference(EntityPersister persister, Serializable pk, Object[] naturalIdValues) { + public Object[] removeNaturalIdCrossReference(EntityPersister persister, Serializable pk, Object[] naturalIdValues, boolean removeOnNaturalIdCache) { persister = locatePersisterForKey( persister ); validateNaturalId( persister, naturalIdValues ); @@ -108,7 +109,7 @@ public Object[] removeNaturalIdCrossReference(EntityPersister persister, Seriali } } - if ( persister.hasNaturalIdCache() ) { + if ( removeOnNaturalIdCache && persister.hasNaturalIdCache() ) { final NaturalIdDataAccess naturalIdCacheAccessStrategy = persister .getNaturalIdCacheAccessStrategy(); final Object naturalIdCacheKey = naturalIdCacheAccessStrategy.generateCacheKey( naturalIdValues, persister, session() ); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/NonNullableTransientDependencies.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/NonNullableTransientDependencies.java index 4d064c73a194..36df2d7afae9 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/NonNullableTransientDependencies.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/NonNullableTransientDependencies.java @@ -24,7 +24,7 @@ public final class NonNullableTransientDependencies { // for the map value. private Map> propertyPathsByTransientEntity; // lazily initialized - void add(String propertyName, Object transientEntity) { + public void add(String propertyName, Object transientEntity) { if ( propertyPathsByTransientEntity == null ) { propertyPathsByTransientEntity = new IdentityHashMap<>(); } 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 8233e275bae4..b0201a8ceba0 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 @@ -73,6 +73,9 @@ import org.jboss.logging.Logger; +import static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptable; +import static org.hibernate.engine.internal.ManagedTypeHelper.isPersistentAttributeInterceptable; + /** * A stateful implementation of the {@link PersistenceContext} contract meaning that we maintain this * state throughout the life of the persistence context. @@ -232,13 +235,9 @@ public void clear() { } ); } - for ( Entry objectEntityEntryEntry : entityEntryContext.reentrantSafeEntityEntries() ) { - if ( objectEntityEntryEntry.getKey() instanceof PersistentAttributeInterceptable ) { - final PersistentAttributeInterceptor interceptor = ( (PersistentAttributeInterceptable) objectEntityEntryEntry.getKey() ).$$_hibernate_getInterceptor(); - if ( interceptor instanceof LazyAttributeLoadingInterceptor ) { - ( (LazyAttributeLoadingInterceptor) interceptor ).unsetSession(); - } - } + for ( Entry objectEntityEntryEntry : entityEntryContext.reentrantSafeEntityEntries() ) {//TODO make this a forEach process within the container + //type-cache-pollution agent: always check for EnhancedEntity type first. + ManagedTypeHelper.processIfPersistentAttributeInterceptable( objectEntityEntryEntry.getKey(), StatefulPersistenceContext::unsetSession, null ); } final SharedSessionContractImplementor session = getSession(); @@ -269,6 +268,13 @@ public void clear() { naturalIdXrefDelegate = null; } + private static void unsetSession(PersistentAttributeInterceptable persistentAttributeInterceptable, Object ignoredParam) { + final PersistentAttributeInterceptor interceptor = persistentAttributeInterceptable.$$_hibernate_getInterceptor(); + if ( interceptor instanceof LazyAttributeLoadingInterceptor ) { + ( (LazyAttributeLoadingInterceptor) interceptor ).unsetSession(); + } + } + @Override public boolean isDefaultReadOnly() { return defaultReadOnly; @@ -605,9 +611,8 @@ public boolean reassociateIfUninitializedProxy(Object value) throws MappingExcep } // or an uninitialized enhanced entity ("bytecode proxy")... - if ( value instanceof PersistentAttributeInterceptable ) { - final PersistentAttributeInterceptable bytecodeProxy = (PersistentAttributeInterceptable) value; - final BytecodeLazyAttributeInterceptor interceptor = (BytecodeLazyAttributeInterceptor) bytecodeProxy.$$_hibernate_getInterceptor(); + if ( isPersistentAttributeInterceptable( value ) ) { + final BytecodeLazyAttributeInterceptor interceptor = (BytecodeLazyAttributeInterceptor) asPersistentAttributeInterceptable( value ).$$_hibernate_getInterceptor(); if ( interceptor != null ) { interceptor.setSession( getSession() ); } @@ -673,9 +678,8 @@ 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(); + else if ( isPersistentAttributeInterceptable( maybeProxy ) ) { + final PersistentAttributeInterceptor interceptor = asPersistentAttributeInterceptable( maybeProxy ).$$_hibernate_getInterceptor(); if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { ( (EnhancementAsProxyLazinessInterceptor) interceptor ).forceInitialize( maybeProxy, null ); } @@ -2113,8 +2117,8 @@ public Object[] removeLocalNaturalIdCrossReference(EntityPersister persister, Se final Object[] localNaturalIdValues = getNaturalIdXrefDelegate().removeNaturalIdCrossReference( persister, id, - naturalIdValues - ); + naturalIdValues, + true); return localNaturalIdValues != null ? localNaturalIdValues : naturalIdValues; } @@ -2231,11 +2235,12 @@ public void cleanupFromSynchronizations() { } @Override - public void handleEviction(Object object, EntityPersister persister, Serializable identifier) { + public void handleEviction(Object object, EntityPersister persister, Serializable identifier, boolean evictOnNaturalIdCache) { getNaturalIdXrefDelegate().removeNaturalIdCrossReference( persister, identifier, - findCachedNaturalId( persister, identifier ) + findCachedNaturalId( persister, identifier ), + evictOnNaturalIdCache ); } }; 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 bef49a41eee7..c9d5714e2c97 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 @@ -93,8 +93,13 @@ protected SqlStatementLogger sqlStatementLogger() { return sqlStatementLogger; } - protected void abortBatch() { - jdbcCoordinator.abortBatch(); + protected void abortBatch(Exception cause) { + try { + jdbcCoordinator.abortBatch(); + } + catch (RuntimeException e) { + cause.addSuppressed( e ); + } } /** 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 c63f5f1aa651..46cd7f6c6a8f 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 @@ -78,10 +78,14 @@ public void addToBatch() { currentStatement.addBatch(); } catch ( SQLException e ) { - abortBatch(); + abortBatch( e ); LOG.debugf( "SQLException escaped proxy", e ); throw sqlExceptionHelper().convert( e, "could not perform addBatch", currentStatementSql ); } + catch (RuntimeException e) { + abortBatch( e ); + throw e; + } statementPosition++; if ( statementPosition >= getKey().getBatchedStatementCount() ) { batchPosition++; @@ -126,12 +130,12 @@ private void performExecution() { checkRowCounts( rowCounts, statement, sql ); } catch ( SQLException e ) { - abortBatch(); + abortBatch( e ); LOG.unableToExecuteBatch( e, sql ); throw sqlExceptionHelper().convert( e, "could not execute batch", sql ); } catch ( RuntimeException re ) { - abortBatch(); + abortBatch( re ); LOG.unableToExecuteBatch( re, sql ); throw re; } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/batch/internal/NonBatchingBatch.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/batch/internal/NonBatchingBatch.java index 224866b295bb..8385ea0384b1 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/batch/internal/NonBatchingBatch.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/batch/internal/NonBatchingBatch.java @@ -49,11 +49,11 @@ public void addToBatch() { jdbcCoordinator.afterStatementExecution(); } catch ( SQLException e ) { - abortBatch(); + abortBatch( e ); throw sqlExceptionHelper().convert( e, "could not execute non-batched batch statement", statementSQL ); } - catch (JDBCException e) { - abortBatch(); + catch (RuntimeException e) { + abortBatch( e ); throw e; } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/BasicConnectionCreator.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/BasicConnectionCreator.java index 00bb69cb4fff..e03481257d09 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/BasicConnectionCreator.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/BasicConnectionCreator.java @@ -8,6 +8,7 @@ import java.sql.Connection; import java.sql.SQLException; +import java.sql.Statement; import java.util.Properties; import org.hibernate.HibernateException; @@ -34,18 +35,21 @@ public abstract class BasicConnectionCreator implements ConnectionCreator { private final boolean autoCommit; private final Integer isolation; + private final String initSql; public BasicConnectionCreator( ServiceRegistryImplementor serviceRegistry, String url, Properties connectionProps, boolean autocommit, - Integer isolation) { + Integer isolation, + String initSql) { this.serviceRegistry = serviceRegistry; this.url = url; this.connectionProps = connectionProps; this.autoCommit = autocommit; this.isolation = isolation; + this.initSql = initSql; } @Override @@ -78,6 +82,15 @@ public Connection createConnection() { throw convertSqlException( "Unable to set auto-commit (" + autoCommit + ")", e ); } + if ( initSql != null && !initSql.trim().isEmpty() ) { + try (Statement s = conn.createStatement()) { + s.execute( initSql ); + } + catch (SQLException e) { + throw convertSqlException( "Unable to execute initSql (" + initSql + ")", e ); + } + } + return conn; } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/ConnectionCreatorBuilder.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/ConnectionCreatorBuilder.java deleted file mode 100644 index 1cfaf3b43add..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/ConnectionCreatorBuilder.java +++ /dev/null @@ -1,62 +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 . - */ -package org.hibernate.engine.jdbc.connections.internal; - -import java.sql.Driver; -import java.util.Properties; - -import org.hibernate.service.spi.ServiceRegistryImplementor; - -/** - * A builder for ConnectionCreator instances - * - * @author Steve Ebersole - */ -public class ConnectionCreatorBuilder { - private final ServiceRegistryImplementor serviceRegistry; - - private Driver driver; - - private String url; - private Properties connectionProps; - - private boolean autoCommit; - private Integer isolation; - - public ConnectionCreatorBuilder(ServiceRegistryImplementor serviceRegistry) { - this.serviceRegistry = serviceRegistry; - } - - public void setDriver(Driver driver) { - this.driver = driver; - } - - public void setUrl(String url) { - this.url = url; - } - - public void setConnectionProps(Properties connectionProps) { - this.connectionProps = connectionProps; - } - - public void setAutoCommit(boolean autoCommit) { - this.autoCommit = autoCommit; - } - - public void setIsolation(Integer isolation) { - this.isolation = isolation; - } - - public ConnectionCreator build() { - if ( driver == null ) { - return new DriverManagerConnectionCreator( serviceRegistry, url, connectionProps, autoCommit, isolation ); - } - else { - return new DriverConnectionCreator( driver, serviceRegistry, url, connectionProps, autoCommit, isolation ); - } - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/ConnectionCreatorFactory.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/ConnectionCreatorFactory.java new file mode 100644 index 000000000000..73b0a760b5c4 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/ConnectionCreatorFactory.java @@ -0,0 +1,32 @@ +/* + * 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.engine.jdbc.connections.internal; + +import java.sql.Driver; +import java.util.Map; +import java.util.Properties; + +import org.hibernate.service.spi.ServiceRegistryImplementor; + +/** + * A factory for {@link ConnectionCreator}. + * + * @author Christian Beikov + */ +interface ConnectionCreatorFactory { + + public ConnectionCreator create( + Driver driver, + ServiceRegistryImplementor serviceRegistry, + String url, + Properties connectionProps, + Boolean autocommit, + Integer isolation, + String initSql, + Map configurationValues); + +} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/ConnectionCreatorFactoryImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/ConnectionCreatorFactoryImpl.java new file mode 100644 index 000000000000..f20bf449864a --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/ConnectionCreatorFactoryImpl.java @@ -0,0 +1,59 @@ +/* + * 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.engine.jdbc.connections.internal; + +import java.sql.Driver; +import java.util.Map; +import java.util.Properties; + +import org.hibernate.service.spi.ServiceRegistryImplementor; + +/** + * The default factory for ConnectionCreator instances + * + * @author Christian Beikov + */ +public class ConnectionCreatorFactoryImpl implements ConnectionCreatorFactory { + + public static final ConnectionCreatorFactory INSTANCE = new ConnectionCreatorFactoryImpl(); + + private ConnectionCreatorFactoryImpl() { + } + + @Override + public ConnectionCreator create( + Driver driver, + ServiceRegistryImplementor serviceRegistry, + String url, + Properties connectionProps, + Boolean autoCommit, + Integer isolation, + String initSql, + Map configurationValues) { + if ( driver == null ) { + return new DriverManagerConnectionCreator( + serviceRegistry, + url, + connectionProps, + autoCommit, + isolation, + initSql + ); + } + else { + return new DriverConnectionCreator( + driver, + serviceRegistry, + url, + connectionProps, + autoCommit, + isolation, + initSql + ); + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DriverConnectionCreator.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DriverConnectionCreator.java index 5ace7c3ab5bc..7d5c5ad24f66 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DriverConnectionCreator.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DriverConnectionCreator.java @@ -27,8 +27,9 @@ public DriverConnectionCreator( String url, Properties connectionProps, Boolean autocommit, - Integer isolation) { - super( serviceRegistry, url, connectionProps, autocommit, isolation ); + Integer isolation, + String initSql) { + super( serviceRegistry, url, connectionProps, autocommit, isolation, initSql ); this.driver = driver; } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DriverManagerConnectionCreator.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DriverManagerConnectionCreator.java index 4b6b70bd19da..b7a22d463e89 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DriverManagerConnectionCreator.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DriverManagerConnectionCreator.java @@ -24,8 +24,9 @@ public DriverManagerConnectionCreator( String url, Properties connectionProps, Boolean autocommit, - Integer isolation) { - super( serviceRegistry, url, connectionProps, autocommit, isolation ); + Integer isolation, + String initSql) { + super( serviceRegistry, url, connectionProps, autocommit, isolation, initSql ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DriverManagerConnectionProviderImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DriverManagerConnectionProviderImpl.java index 1a999ed00625..7201b7e68928 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DriverManagerConnectionProviderImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DriverManagerConnectionProviderImpl.java @@ -22,6 +22,7 @@ import org.hibernate.HibernateException; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.Database; import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; @@ -55,6 +56,8 @@ public class DriverManagerConnectionProviderImpl public static final String INITIAL_SIZE = "hibernate.connection.initial_pool_size"; // in TimeUnit.SECONDS public static final String VALIDATION_INTERVAL = "hibernate.connection.pool_validation_interval"; + public static final String INIT_SQL ="hibernate.connection.init_sql"; + public static final String CONNECTION_CREATOR_FACTORY ="hibernate.connection.creator_factory_class"; private volatile PoolState state; @@ -99,18 +102,19 @@ private PooledConnections buildPool(Map configurationValues, ServiceRegistryImpl } private static ConnectionCreator buildCreator(Map configurationValues, ServiceRegistryImplementor serviceRegistry) { - final ConnectionCreatorBuilder connectionCreatorBuilder = new ConnectionCreatorBuilder( serviceRegistry ); + final String url = (String) configurationValues.get( AvailableSettings.URL ); - final String driverClassName = (String) configurationValues.get( AvailableSettings.DRIVER ); - connectionCreatorBuilder.setDriver( loadDriverIfPossible( driverClassName, serviceRegistry ) ); + String driverClassName = (String) configurationValues.get( AvailableSettings.DRIVER ); + Driver driver = null; + if ( driverClassName != null ) { + driver = loadDriverIfPossible( driverClassName, serviceRegistry ); + } - final String url = (String) configurationValues.get( AvailableSettings.URL ); if ( url == null ) { final String msg = log.jdbcUrlNotSpecified( AvailableSettings.URL ); log.error( msg ); throw new HibernateException( msg ); } - connectionCreatorBuilder.setUrl( url ); log.usingDriver( driverClassName, url ); @@ -123,19 +127,38 @@ private static ConnectionCreator buildCreator(Map configurationValues, ServiceRe else { log.connectionProperties( ConfigurationHelper.maskOut( connectionProps, "password" ) ); } - connectionCreatorBuilder.setConnectionProps( connectionProps ); final boolean autoCommit = ConfigurationHelper.getBoolean( AvailableSettings.AUTOCOMMIT, configurationValues, false ); log.autoCommitMode( autoCommit ); - connectionCreatorBuilder.setAutoCommit( autoCommit ); final Integer isolation = ConnectionProviderInitiator.extractIsolation( configurationValues ); if ( isolation != null ) { log.jdbcIsolationLevel( ConnectionProviderInitiator.toIsolationNiceName( isolation ) ); } - connectionCreatorBuilder.setIsolation( isolation ); - return connectionCreatorBuilder.build(); + final String initSql = (String) configurationValues.get( INIT_SQL ); + + final Object connectionCreatorFactory = configurationValues.get( CONNECTION_CREATOR_FACTORY ); + ConnectionCreatorFactory factory = null; + if ( connectionCreatorFactory instanceof ConnectionCreatorFactory ) { + factory = (ConnectionCreatorFactory) connectionCreatorFactory; + } + else if ( connectionCreatorFactory != null ) { + factory = loadConnectionCreatorFactory( connectionCreatorFactory.toString(), serviceRegistry ); + } + if ( factory == null ) { + factory = ConnectionCreatorFactoryImpl.INSTANCE; + } + return factory.create( + driver, + serviceRegistry, + url, + connectionProps, + autoCommit, + isolation, + initSql, + configurationValues + ); } private static Driver loadDriverIfPossible(String driverClassName, ServiceRegistryImplementor serviceRegistry) { @@ -163,6 +186,31 @@ private static Driver loadDriverIfPossible(String driverClassName, ServiceRegist } } + private static ConnectionCreatorFactory loadConnectionCreatorFactory(String connectionCreatorFactoryClassName, ServiceRegistryImplementor serviceRegistry) { + if ( connectionCreatorFactoryClassName == null ) { + log.debug( "No connection creator factory class specified" ); + return null; + } + + if ( serviceRegistry != null ) { + final ClassLoaderService classLoaderService = serviceRegistry.getService( ClassLoaderService.class ); + final Class factoryClass = classLoaderService.classForName( connectionCreatorFactoryClassName ); + try { + return factoryClass.newInstance(); + } + catch ( Exception e ) { + throw new ServiceException( "Specified ConnectionCreatorFactory " + connectionCreatorFactoryClassName + " could not be loaded", e ); + } + } + + try { + return (ConnectionCreatorFactory) Class.forName( connectionCreatorFactoryClassName ).newInstance(); + } + catch ( Exception e1 ) { + throw new ServiceException( "Specified ConnectionCreatorFactory " + connectionCreatorFactoryClassName + " could not be loaded", e1 ); + } + } + // use the pool ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/ExtractedDatabaseMetaDataImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/ExtractedDatabaseMetaDataImpl.java index 1ac0389086f7..aaacedfa7f93 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/ExtractedDatabaseMetaDataImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/ExtractedDatabaseMetaDataImpl.java @@ -168,7 +168,8 @@ public static class Builder { private boolean supportsNamedParameters; private boolean supportsScrollableResults; private boolean supportsGetGeneratedKeys; - private boolean supportsBatchUpdates; + // In absence of DatabaseMetaData batching updates is assumed to be supported + private boolean supportsBatchUpdates = true; private boolean supportsDataDefinitionInTransaction; private boolean doesDataDefinitionCauseTransactionCommit; private SQLStateType sqlStateType; diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jndi/internal/JndiServiceImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jndi/internal/JndiServiceImpl.java index 001a52a8d5e5..fdd8f369cfaf 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jndi/internal/JndiServiceImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jndi/internal/JndiServiceImpl.java @@ -119,7 +119,7 @@ private Name parseName(String jndiName, Context context) { try { final URI uri = new URI( jndiName ); final String scheme = uri.getScheme(); - if ( scheme != null && (! "java".equals( scheme ) ) ) { + if ( scheme != null && (! allowedScheme( scheme ) ) ) { throw new JndiException( "JNDI lookups for scheme '" + scheme + "' are not allowed" ); } } @@ -137,6 +137,16 @@ private Name parseName(String jndiName, Context context) { } } + private static boolean allowedScheme(final String scheme) { + switch ( scheme ) { + case "java" : + case "osgi" : + return true; + default: + return false; + } + } + private void cleanUp(InitialContext initialContext) { try { initialContext.close(); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/query/spi/HQLQueryPlan.java b/hibernate-core/src/main/java/org/hibernate/engine/query/spi/HQLQueryPlan.java index f75d741ed74c..71dc41ef584a 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/query/spi/HQLQueryPlan.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/query/spi/HQLQueryPlan.java @@ -103,8 +103,7 @@ protected HQLQueryPlan( final Set combinedQuerySpaces = new HashSet<>(); final Map querySubstitutions = factory.getSessionFactoryOptions().getQuerySubstitutions(); - final QueryTranslatorFactory queryTranslatorFactory = factory.getServiceRegistry().getService( QueryTranslatorFactory.class ); - + final QueryTranslatorFactory queryTranslatorFactory = factory.getFastSessionServices().queryTranslatorFactory; for ( int i=0; i getInitializedLazyAttributeNames() { @Deprecated default void attributeInitialized(String name) { } + + /** + * + * Callback from the enhanced class that an attribute has been loaded + * + * @deprecated Interceptors that deal with + * * lazy state should implement {@link BytecodeLazyAttributeInterceptor} + * + * @param fieldName + * @return true id the attribute is loaded false otherwise + */ + @Deprecated + default boolean isAttributeLoaded(String fieldName){ + return false; + } } 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 c00719ed03a1..399337d3c6fa 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 @@ -1030,6 +1030,11 @@ public String getEntityName(Object object) { return delegate.getEntityName( object ); } + @Override + public T getReference(T object) { + return delegate.getReference( object ); + } + @Override public IdentifierLoadAccess byId(String entityName) { return delegate.byId( entityName ); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionLazyDelegator.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionLazyDelegator.java new file mode 100644 index 000000000000..3641f67c771d --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionLazyDelegator.java @@ -0,0 +1,792 @@ +/* + * 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.engine.spi; + +import java.io.Serializable; +import java.sql.Connection; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; + +import javax.persistence.EntityGraph; +import javax.persistence.EntityManagerFactory; +import javax.persistence.FlushModeType; +import javax.persistence.LockModeType; +import javax.persistence.StoredProcedureQuery; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaDelete; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.CriteriaUpdate; +import javax.persistence.metamodel.Metamodel; + +import org.hibernate.CacheMode; +import org.hibernate.Criteria; +import org.hibernate.Filter; +import org.hibernate.FlushMode; +import org.hibernate.HibernateException; +import org.hibernate.IdentifierLoadAccess; +import org.hibernate.LobHelper; +import org.hibernate.LockMode; +import org.hibernate.LockOptions; +import org.hibernate.MultiIdentifierLoadAccess; +import org.hibernate.NaturalIdLoadAccess; +import org.hibernate.Query; +import org.hibernate.ReplicationMode; +import org.hibernate.Session; +import org.hibernate.SessionEventListener; +import org.hibernate.SessionFactory; +import org.hibernate.SharedSessionBuilder; +import org.hibernate.SimpleNaturalIdLoadAccess; +import org.hibernate.Transaction; +import org.hibernate.TypeHelper; +import org.hibernate.UnknownProfileException; +import org.hibernate.graph.RootGraph; +import org.hibernate.jdbc.ReturningWork; +import org.hibernate.jdbc.Work; +import org.hibernate.procedure.ProcedureCall; +import org.hibernate.query.NativeQuery; +import org.hibernate.stat.SessionStatistics; + +/** + * This helper class allows decorating a Session instance, while the + * instance itself is lazily provided via a {@code Supplier}. + * When the decorated instance is readily available, one + * should prefer using {@code SessionDelegatorBaseImpl}. + * + * Another difference with SessionDelegatorBaseImpl is that + * this type only implements Session. + * + * @author Sanne Grinovero (C) 2022 Red Hat Inc. + */ +public class SessionLazyDelegator implements Session { + + private final Supplier lazySession; + + public SessionLazyDelegator(Supplier lazySessionLookup){ + this.lazySession = lazySessionLookup; + } + + @Override + public SharedSessionBuilder sessionWithOptions() { + return lazySession.get().sessionWithOptions(); + } + + @Override + public void flush() throws HibernateException { + lazySession.get().flush(); + } + + @Override + @Deprecated + public void setFlushMode(FlushMode flushMode) { + lazySession.get().setFlushMode( flushMode ); + } + + @Override + public FlushModeType getFlushMode() { + return lazySession.get().getFlushMode(); + } + + @Override + public void setHibernateFlushMode(FlushMode flushMode) { + lazySession.get().setHibernateFlushMode( flushMode ); + } + + @Override + public FlushMode getHibernateFlushMode() { + return lazySession.get().getHibernateFlushMode(); + } + + @Override + public void setCacheMode(CacheMode cacheMode) { + lazySession.get().setCacheMode( cacheMode ); + } + + @Override + public CacheMode getCacheMode() { + return lazySession.get().getCacheMode(); + } + + @Override + public SessionFactory getSessionFactory() { + return lazySession.get().getSessionFactory(); + } + + @Override + public void cancelQuery() throws HibernateException { + lazySession.get().cancelQuery(); + } + + @Override + public boolean isDirty() throws HibernateException { + return lazySession.get().isDirty(); + } + + @Override + public boolean isDefaultReadOnly() { + return lazySession.get().isDefaultReadOnly(); + } + + @Override + public void setDefaultReadOnly(boolean readOnly) { + lazySession.get().setDefaultReadOnly( readOnly ); + } + + @Override + public Serializable getIdentifier(Object object) { + return lazySession.get().getIdentifier( object ); + } + + @Override + public boolean contains(String entityName, Object object) { + return lazySession.get().contains( entityName, object ); + } + + @Override + public void evict(Object object) { + lazySession.get().evict( object ); + } + + @Override + public T load(Class theClass, Serializable id, LockMode lockMode) { + return lazySession.get().load( theClass, id, lockMode ); + } + + @Override + public T load(Class theClass, Serializable id, LockOptions lockOptions) { + return lazySession.get().load( theClass, id, lockOptions ); + } + + @Override + public Object load(String entityName, Serializable id, LockMode lockMode) { + return lazySession.get().load( entityName, id, lockMode ); + } + + @Override + public Object load(String entityName, Serializable id, LockOptions lockOptions) { + return lazySession.get().load( entityName, id, lockOptions ); + } + + @Override + public T load(Class theClass, Serializable id) { + return lazySession.get().load( theClass, id ); + } + + @Override + public Object load(String entityName, Serializable id) { + return lazySession.get().load( entityName, id ); + } + + @Override + public void load(Object object, Serializable id) { + lazySession.get().load( object, id ); + } + + @Override + public void replicate(Object object, ReplicationMode replicationMode) { + lazySession.get().replicate( object, replicationMode ); + } + + @Override + public void replicate(String entityName, Object object, ReplicationMode replicationMode) { + lazySession.get().replicate( entityName, object, replicationMode ); + } + + @Override + public Serializable save(Object object) { + return lazySession.get().save( object ); + } + + @Override + public Serializable save(String entityName, Object object) { + return lazySession.get().save( entityName, object ); + } + + @Override + public void saveOrUpdate(Object object) { + lazySession.get().saveOrUpdate( object ); + } + + @Override + public void saveOrUpdate(String entityName, Object object) { + lazySession.get().saveOrUpdate( entityName, object ); + } + + @Override + public void update(Object object) { + lazySession.get().update( object ); + } + + @Override + public void update(String entityName, Object object) { + lazySession.get().update( entityName, object ); + } + + @Override + public Object merge(Object object) { + return lazySession.get().merge( object ); + } + + @Override + public Object merge(String entityName, Object object) { + return lazySession.get().merge( entityName, object ); + } + + @Override + public void persist(Object object) { + lazySession.get().persist( object ); + } + + @Override + public void persist(String entityName, Object object) { + lazySession.get().persist( entityName, object ); + } + + @Override + public void delete(Object object) { + lazySession.get().delete( object ); + } + + @Override + public void delete(String entityName, Object object) { + lazySession.get().delete( entityName, object ); + } + + @Override + public void lock(Object object, LockMode lockMode) { + lazySession.get().lock( object, lockMode ); + } + + @Override + public void lock(String entityName, Object object, LockMode lockMode) { + lazySession.get().lock( entityName, object, lockMode ); + } + + @Override + public LockRequest buildLockRequest(LockOptions lockOptions) { + return lazySession.get().buildLockRequest( lockOptions ); + } + + @Override + public void refresh(Object object) { + lazySession.get().refresh( object ); + } + + @Override + public void refresh(String entityName, Object object) { + lazySession.get().refresh( entityName, object ); + } + + @Override + public void refresh(Object object, LockMode lockMode) { + lazySession.get().refresh( object, lockMode ); + } + + @Override + public void refresh(Object object, LockOptions lockOptions) { + lazySession.get().refresh( object, lockOptions ); + } + + @Override + public void refresh(String entityName, Object object, LockOptions lockOptions) { + lazySession.get().refresh( entityName, object, lockOptions ); + } + + @Override + public LockMode getCurrentLockMode(Object object) { + return lazySession.get().getCurrentLockMode( object ); + } + + @Override + @Deprecated + public Query createFilter(Object collection, String queryString) { + return lazySession.get().createFilter( collection, queryString ); + } + + @Override + public void clear() { + lazySession.get().clear(); + } + + @Override + public T get(Class entityType, Serializable id) { + return lazySession.get().get( entityType, id ); + } + + @Override + public T get(Class entityType, Serializable id, LockMode lockMode) { + return lazySession.get().get( entityType, id, lockMode ); + } + + @Override + public T get(Class entityType, Serializable id, LockOptions lockOptions) { + return lazySession.get().get( entityType, id, lockOptions ); + } + + @Override + public Object get(String entityName, Serializable id) { + return lazySession.get().get( entityName, id ); + } + + @Override + public Object get(String entityName, Serializable id, LockMode lockMode) { + return lazySession.get().get( entityName, id, lockMode ); + } + + @Override + public Object get(String entityName, Serializable id, LockOptions lockOptions) { + return lazySession.get().get( entityName, id, lockOptions ); + } + + @Override + public String getEntityName(Object object) { + return lazySession.get().getEntityName( object ); + } + + @Override + public T getReference(T object) { + return lazySession.get().getReference( object ); + } + + @Override + public IdentifierLoadAccess byId(String entityName) { + return lazySession.get().byId( entityName ); + } + + @Override + public MultiIdentifierLoadAccess byMultipleIds(Class entityClass) { + return lazySession.get().byMultipleIds( entityClass ); + } + + @Override + public MultiIdentifierLoadAccess byMultipleIds(String entityName) { + return lazySession.get().byMultipleIds( entityName ); + } + + @Override + public IdentifierLoadAccess byId(Class entityClass) { + return lazySession.get().byId( entityClass ); + } + + @Override + public NaturalIdLoadAccess byNaturalId(String entityName) { + return lazySession.get().byNaturalId( entityName ); + } + + @Override + public NaturalIdLoadAccess byNaturalId(Class entityClass) { + return lazySession.get().byNaturalId( entityClass ); + } + + @Override + public SimpleNaturalIdLoadAccess bySimpleNaturalId(String entityName) { + return lazySession.get().bySimpleNaturalId( entityName ); + } + + @Override + public SimpleNaturalIdLoadAccess bySimpleNaturalId(Class entityClass) { + return lazySession.get().bySimpleNaturalId( entityClass ); + } + + @Override + public Filter enableFilter(String filterName) { + return lazySession.get().enableFilter( filterName ); + } + + @Override + public Filter getEnabledFilter(String filterName) { + return lazySession.get().getEnabledFilter( filterName ); + } + + @Override + public void disableFilter(String filterName) { + lazySession.get().disableFilter( filterName ); + } + + @Override + public SessionStatistics getStatistics() { + return lazySession.get().getStatistics(); + } + + @Override + public boolean isReadOnly(Object entityOrProxy) { + return lazySession.get().isReadOnly( entityOrProxy ); + } + + @Override + public void setReadOnly(Object entityOrProxy, boolean readOnly) { + lazySession.get().setReadOnly( entityOrProxy, readOnly ); + } + + @Override + public RootGraph createEntityGraph(Class rootType) { + return lazySession.get().createEntityGraph( rootType ); + } + + @Override + public RootGraph createEntityGraph(String graphName) { + return lazySession.get().createEntityGraph( graphName ); + } + + @Override + public RootGraph getEntityGraph(String graphName) { + return lazySession.get().getEntityGraph( graphName ); + } + + @Override + public List> getEntityGraphs(Class entityClass) { + return lazySession.get().getEntityGraphs( entityClass ); + } + + @Override + public Connection disconnect() { + return lazySession.get().disconnect(); + } + + @Override + public void reconnect(Connection connection) { + lazySession.get().reconnect( connection ); + } + + @Override + public boolean isFetchProfileEnabled(String name) throws UnknownProfileException { + return lazySession.get().isFetchProfileEnabled( name ); + } + + @Override + public void enableFetchProfile(String name) throws UnknownProfileException { + lazySession.get().enableFetchProfile( name ); + } + + @Override + public void disableFetchProfile(String name) throws UnknownProfileException { + lazySession.get().disableFetchProfile( name ); + } + + @Override + public TypeHelper getTypeHelper() { + return lazySession.get().getTypeHelper(); + } + + @Override + public LobHelper getLobHelper() { + return lazySession.get().getLobHelper(); + } + + @Override + public void addEventListeners(SessionEventListener... listeners) { + lazySession.get().addEventListeners( listeners ); + } + + @Override + public org.hibernate.query.Query createQuery(String queryString, Class resultType) { + return lazySession.get().createQuery( queryString, resultType ); + } + + @Override + public org.hibernate.query.Query createQuery(CriteriaQuery criteriaQuery) { + return lazySession.get().createQuery( criteriaQuery ); + } + + @Override + public org.hibernate.query.Query createQuery(CriteriaUpdate updateQuery) { + return lazySession.get().createQuery( updateQuery ); + } + + @Override + public org.hibernate.query.Query createQuery(CriteriaDelete deleteQuery) { + return lazySession.get().createQuery( deleteQuery ); + } + + @Override + public org.hibernate.query.Query createNamedQuery(String name, Class resultType) { + return lazySession.get().createNamedQuery( name, resultType ); + } + + @Override + public NativeQuery createSQLQuery(String queryString) { + return lazySession.get().createSQLQuery( queryString ); + } + + @Override + public String getTenantIdentifier() { + return lazySession.get().getTenantIdentifier(); + } + + @Override + public void close() throws HibernateException { + lazySession.get().close(); + } + + @Override + public boolean isOpen() { + return lazySession.get().isOpen(); + } + + @Override + public boolean isConnected() { + return lazySession.get().isConnected(); + } + + @Override + public Transaction beginTransaction() { + return lazySession.get().beginTransaction(); + } + + @Override + public Transaction getTransaction() { + return lazySession.get().getTransaction(); + } + + @Override + public org.hibernate.query.Query createQuery(String queryString) { + return lazySession.get().createQuery( queryString ); + } + + @Override + public org.hibernate.query.Query getNamedQuery(String queryName) { + return lazySession.get().getNamedQuery( queryName ); + } + + @Override + public ProcedureCall getNamedProcedureCall(String name) { + return lazySession.get().getNamedProcedureCall( name ); + } + + @Override + public ProcedureCall createStoredProcedureCall(String procedureName) { + return lazySession.get().createStoredProcedureCall( procedureName ); + } + + @Override + public ProcedureCall createStoredProcedureCall(String procedureName, Class... resultClasses) { + return lazySession.get().createStoredProcedureCall( procedureName, resultClasses ); + } + + @Override + public ProcedureCall createStoredProcedureCall(String procedureName, String... resultSetMappings) { + return lazySession.get().createStoredProcedureCall( procedureName, resultSetMappings ); + } + + @Override + @Deprecated + public Criteria createCriteria(Class persistentClass) { + return lazySession.get().createCriteria( persistentClass ); + } + + @Override + @Deprecated + public Criteria createCriteria(Class persistentClass, String alias) { + return lazySession.get().createCriteria( persistentClass, alias ); + } + + @Override + @Deprecated + public Criteria createCriteria(String entityName) { + return lazySession.get().createCriteria( entityName ); + } + + @Override + @Deprecated + public Criteria createCriteria(String entityName, String alias) { + return lazySession.get().createCriteria( entityName, alias ); + } + + @Override + public Integer getJdbcBatchSize() { + return lazySession.get().getJdbcBatchSize(); + } + + @Override + public void setJdbcBatchSize(Integer jdbcBatchSize) { + lazySession.get().setJdbcBatchSize( jdbcBatchSize ); + } + + @Override + public void doWork(Work work) throws HibernateException { + lazySession.get().doWork( work ); + } + + @Override + public T doReturningWork(ReturningWork work) throws HibernateException { + return lazySession.get().doReturningWork( work ); + } + + @Override + public org.hibernate.query.Query createNamedQuery(String name) { + return lazySession.get().createNamedQuery( name ); + } + + @Override + public NativeQuery createNativeQuery(String sqlString) { + return lazySession.get().createNativeQuery( sqlString ); + } + + @Override + public NativeQuery createNativeQuery(String sqlString, String resultSetMapping) { + return lazySession.get().createNativeQuery( sqlString, resultSetMapping ); + } + + @Override + @Deprecated + public Query getNamedSQLQuery(String name) { + return lazySession.get().getNamedSQLQuery( name ); + } + + @Override + public NativeQuery getNamedNativeQuery(String name) { + return lazySession.get().getNamedNativeQuery( name ); + } + @Override + public void remove(Object entity) { + lazySession.get().remove( entity ); + } + @Override + public T find(Class entityClass, Object primaryKey) { + return lazySession.get().find( entityClass, primaryKey ); + } + + @Override + public T find(Class entityClass, Object primaryKey, Map properties) { + return lazySession.get().find( entityClass, primaryKey, properties ); + } + + @Override + public T find(Class entityClass, Object primaryKey, LockModeType lockMode) { + return lazySession.get().find( entityClass, primaryKey, lockMode ); + } + + @Override + public T find(Class entityClass, Object primaryKey, LockModeType lockMode, Map properties) { + return lazySession.get().find( entityClass, primaryKey, lockMode, properties ); + } + + @Override + public T getReference(Class entityClass, Object primaryKey) { + return lazySession.get().getReference( entityClass, primaryKey ); + } + + @Override + public void setFlushMode(FlushModeType flushMode) { + lazySession.get().setFlushMode( flushMode ); + } + + @Override + public void lock(Object entity, LockModeType lockMode) { + lazySession.get().lock( entity, lockMode ); + } + + @Override + public void lock(Object entity, LockModeType lockMode, Map properties) { + lazySession.get().lock( entity, lockMode, properties ); + } + + @Override + public void refresh(Object entity, Map properties) { + lazySession.get().refresh( entity, properties ); + } + + @Override + public void refresh(Object entity, LockModeType lockMode) { + lazySession.get().refresh( entity, lockMode ); + } + + @Override + public void refresh(Object entity, LockModeType lockMode, Map properties) { + lazySession.get().refresh( entity, lockMode, properties ); + } + + @Override + public void detach(Object entity) { + lazySession.get().detach( entity ); + } + + @Override + public boolean contains(Object entity) { + return lazySession.get().contains( entity ); + } + + @Override + public LockModeType getLockMode(Object entity) { + return lazySession.get().getLockMode( entity ); + } + + @Override + public void setProperty(String propertyName, Object value) { + lazySession.get().setProperty( propertyName, value ); + } + + @Override + public Map getProperties() { + return lazySession.get().getProperties(); + } + + @Override + public NativeQuery createNativeQuery(String sqlString, Class resultClass) { + return lazySession.get().createNativeQuery( sqlString, resultClass ); + } + + @Override + public StoredProcedureQuery createNamedStoredProcedureQuery(String name) { + return lazySession.get().createNamedStoredProcedureQuery( name ); + } + + @Override + public StoredProcedureQuery createStoredProcedureQuery(String procedureName) { + return lazySession.get().createStoredProcedureQuery( procedureName ); + } + + @Override + public StoredProcedureQuery createStoredProcedureQuery(String procedureName, Class... resultClasses) { + return lazySession.get().createStoredProcedureQuery( procedureName, resultClasses ); + } + + @Override + public StoredProcedureQuery createStoredProcedureQuery(String procedureName, String... resultSetMappings) { + return lazySession.get().createStoredProcedureQuery( procedureName, resultSetMappings ); + } + + @Override + public void joinTransaction() { + lazySession.get().joinTransaction(); + } + + @Override + public boolean isJoinedToTransaction() { + return lazySession.get().isJoinedToTransaction(); + } + + @Override + public T unwrap(Class cls) { + return lazySession.get().unwrap( cls ); + } + + @Override + public Object getDelegate() { + return lazySession.get().getDelegate(); + } + + @Override + public EntityManagerFactory getEntityManagerFactory() { + return lazySession.get().getEntityManagerFactory(); + } + + @Override + public CriteriaBuilder getCriteriaBuilder() { + return lazySession.get().getCriteriaBuilder(); + } + + @Override + public Metamodel getMetamodel() { + return lazySession.get().getMetamodel(); + } + + @Override + public Session getSession() { + return lazySession.get().getSession(); + } +} 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 faf3394df49f..bfc764bafca4 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 @@ -167,7 +167,10 @@ default void checkOpen() { * @apiNote This "timestamp" need not be related to timestamp in the Java Date/millisecond * sense. It just needs to be an incrementing value. See * {@link CacheTransactionSynchronization#getCurrentTransactionStartTimestamp()} + * + * @deprecated no longer supported, when the Second Level Cache is enabled {{@link CacheTransactionSynchronization#getCachingTimestamp()}} can be used. */ + @Deprecated long getTransactionStartTimestamp(); /** 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 35b16972b302..e2e8d6492486 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 @@ -17,6 +17,7 @@ import org.hibernate.classic.Lifecycle; import org.hibernate.engine.internal.Cascade; import org.hibernate.engine.internal.CascadePoint; +import org.hibernate.engine.internal.ManagedTypeHelper; import org.hibernate.engine.internal.Versioning; import org.hibernate.engine.spi.CascadingAction; import org.hibernate.engine.spi.EntityEntry; @@ -107,9 +108,7 @@ protected Serializable saveWithGeneratedId( boolean requiresImmediateIdAccess) { callbackRegistry.preCreate( entity ); - if ( entity instanceof SelfDirtinessTracker ) { - ( (SelfDirtinessTracker) entity ).$$_hibernate_clearDirtyAttributes(); - } + ManagedTypeHelper.processIfSelfDirtinessTracker( entity, SelfDirtinessTracker::$$_hibernate_clearDirtyAttributes ); EntityPersister persister = source.getEntityPersister( entityName, entity ); Serializable generatedId = persister.getIdentifierGenerator().generate( source, entity ); 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 0ef2f2fc3f33..2f9019ce38fa 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 @@ -113,8 +113,8 @@ protected void doEvict( persistenceContext.getNaturalIdHelper().handleEviction( object, persister, - key.getIdentifier() - ); + key.getIdentifier(), + false); } // remove all collections for the entity from the session-level cache 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 ef9815b00c09..9f639075edb1 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 @@ -18,10 +18,12 @@ 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.ManagedTypeHelper; 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.Managed; import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.PersistentAttributeInterceptable; import org.hibernate.engine.spi.PersistentAttributeInterceptor; @@ -92,8 +94,10 @@ private void checkNaturalId( Object[] current, Object[] loaded, SessionImplementor session) { - if ( entity instanceof PersistentAttributeInterceptable ) { - final PersistentAttributeInterceptor interceptor = ( (PersistentAttributeInterceptable) entity ).$$_hibernate_getInterceptor(); + if ( ManagedTypeHelper.isPersistentAttributeInterceptable( entity ) ) { + final PersistentAttributeInterceptable asPersistentAttributeInterceptable = ManagedTypeHelper.asPersistentAttributeInterceptable( + entity ); + final PersistentAttributeInterceptor interceptor = asPersistentAttributeInterceptable.$$_hibernate_getInterceptor(); if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { // EARLY EXIT!!! // nothing to check - the entity is an un-initialized enhancement-as-proxy reference @@ -247,9 +251,7 @@ private boolean isUpdateNecessary(final FlushEntityEvent event, final boolean mi return true; } else { - if ( SelfDirtinessTracker.class.isInstance( event.getEntity() ) ) { - ( (SelfDirtinessTracker) event.getEntity() ).$$_hibernate_clearDirtyAttributes(); - } + ManagedTypeHelper.processIfSelfDirtinessTracker( event.getEntity(), SelfDirtinessTracker::$$_hibernate_clearDirtyAttributes ); event.getSession() .getFactory() .getCustomEntityDirtinessStrategy() @@ -526,14 +528,14 @@ protected void dirtyCheck(final FlushEntityEvent event) throws HibernateExceptio persister.getPropertyNames(), persister.getPropertyTypes() ); - if ( dirtyProperties == null ) { - if ( entity instanceof SelfDirtinessTracker ) { - if ( ( (SelfDirtinessTracker) entity ).$$_hibernate_hasDirtyAttributes() || persister.hasMutableProperties() ) { + if ( ManagedTypeHelper.isSelfDirtinessTracker( entity ) ) { + final SelfDirtinessTracker asSelfDirtinessTracker = ManagedTypeHelper.asSelfDirtinessTracker( entity ); + if ( asSelfDirtinessTracker.$$_hibernate_hasDirtyAttributes() || persister.hasMutableProperties() ) { dirtyProperties = persister.resolveDirtyAttributeIndexes( values, loadedState, - ( (SelfDirtinessTracker) entity ).$$_hibernate_getDirtyAttributes(), + asSelfDirtinessTracker.$$_hibernate_getDirtyAttributes(), session ); } 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 06d384b36114..e2579939deaf 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,12 @@ import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; import org.hibernate.engine.internal.Cascade; import org.hibernate.engine.internal.CascadePoint; +import org.hibernate.engine.internal.ManagedTypeHelper; 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.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; @@ -42,6 +42,10 @@ import org.hibernate.type.ForeignKeyDirection; import org.hibernate.type.TypeHelper; +import static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptable; +import static org.hibernate.engine.internal.ManagedTypeHelper.asSelfDirtinessTracker; +import static org.hibernate.engine.internal.ManagedTypeHelper.isSelfDirtinessTracker; + /** * Defines the default copy event listener used by hibernate for copying entities * in response to generated copy events. @@ -111,9 +115,8 @@ public void onMerge(MergeEvent event, Map copiedAlready) throws HibernateExcepti entity = li.getImplementation(); } } - else if ( original instanceof PersistentAttributeInterceptable ) { - final PersistentAttributeInterceptable interceptable = (PersistentAttributeInterceptable) original; - final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor(); + else if ( ManagedTypeHelper.isPersistentAttributeInterceptable( original ) ) { + final PersistentAttributeInterceptor interceptor = asPersistentAttributeInterceptable( original ).$$_hibernate_getInterceptor(); if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { final EnhancementAsProxyLazinessInterceptor proxyInterceptor = (EnhancementAsProxyLazinessInterceptor) interceptor; LOG.trace( "Ignoring uninitialized enhanced-proxy" ); @@ -249,9 +252,8 @@ protected void entityIsTransient(MergeEvent event, Map copyCache) { event.setResult( copy ); - if ( copy instanceof PersistentAttributeInterceptable ) { - final PersistentAttributeInterceptable interceptable = (PersistentAttributeInterceptable) copy; - final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor(); + if ( ManagedTypeHelper.isPersistentAttributeInterceptable( copy ) ) { + final PersistentAttributeInterceptor interceptor = asPersistentAttributeInterceptable( copy ).$$_hibernate_getInterceptor(); if ( interceptor == null ) { persister.getBytecodeEnhancementMetadata().injectInterceptor( copy, id, session ); } @@ -363,11 +365,11 @@ private Object unproxyManagedForDetachedMerging( return source.getPersistenceContextInternal().unproxy( managed ); } - if ( incoming instanceof PersistentAttributeInterceptable + if ( ManagedTypeHelper.isPersistentAttributeInterceptable( incoming ) && persister.getBytecodeEnhancementMetadata().isEnhancedForLazyLoading() ) { - final PersistentAttributeInterceptor incomingInterceptor = ( (PersistentAttributeInterceptable) incoming ).$$_hibernate_getInterceptor(); - final PersistentAttributeInterceptor managedInterceptor = ( (PersistentAttributeInterceptable) managed ).$$_hibernate_getInterceptor(); + final PersistentAttributeInterceptor incomingInterceptor = asPersistentAttributeInterceptable( incoming ).$$_hibernate_getInterceptor(); + final PersistentAttributeInterceptor managedInterceptor = asPersistentAttributeInterceptable( managed ).$$_hibernate_getInterceptor(); // todo - do we need to specially handle the case where both `incoming` and `managed` are initialized, but // with different attributes initialized? @@ -392,12 +394,13 @@ private Object unproxyManagedForDetachedMerging( 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 ) { + if ( isSelfDirtinessTracker( entity ) && isSelfDirtinessTracker( target ) ) { // clear, because setting the embedded attributes dirties them - ( (SelfDirtinessTracker) target ).$$_hibernate_clearDirtyAttributes(); + final SelfDirtinessTracker castedTarget = asSelfDirtinessTracker( target ); + castedTarget.$$_hibernate_clearDirtyAttributes(); - for ( String fieldName : ( (SelfDirtinessTracker) entity ).$$_hibernate_getDirtyAttributes() ) { - ( (SelfDirtinessTracker) target ).$$_hibernate_trackChange( fieldName ); + for ( String fieldName : asSelfDirtinessTracker( entity ).$$_hibernate_getDirtyAttributes() ) { + castedTarget.$$_hibernate_trackChange( fieldName ); } } } diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DirtyCollectionSearchVisitor.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DirtyCollectionSearchVisitor.java index 3cfcd164171e..a9f30e5b6608 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DirtyCollectionSearchVisitor.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DirtyCollectionSearchVisitor.java @@ -9,6 +9,7 @@ import org.hibernate.HibernateException; import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; import org.hibernate.collection.spi.PersistentCollection; +import org.hibernate.engine.internal.ManagedTypeHelper; import org.hibernate.engine.spi.PersistentAttributeInterceptable; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.event.spi.EventSource; @@ -31,8 +32,8 @@ public class DirtyCollectionSearchVisitor extends AbstractVisitor { public DirtyCollectionSearchVisitor(Object entity, EventSource session, boolean[] propertyVersionability) { super( session ); EnhancementAsProxyLazinessInterceptor interceptor = null; - if ( entity instanceof PersistentAttributeInterceptable ) { - if ( ( (PersistentAttributeInterceptable) entity ).$$_hibernate_getInterceptor() instanceof EnhancementAsProxyLazinessInterceptor ) { + if ( ManagedTypeHelper.isPersistentAttributeInterceptable( entity ) ) { + if ( ManagedTypeHelper.asPersistentAttributeInterceptable( entity ).$$_hibernate_getInterceptor() instanceof EnhancementAsProxyLazinessInterceptor ) { interceptor = (EnhancementAsProxyLazinessInterceptor) ( (PersistentAttributeInterceptable) entity ).$$_hibernate_getInterceptor(); } } 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 bf6193aaa392..4d99dbcca581 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 @@ -13,6 +13,7 @@ import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; import org.hibernate.collection.spi.PersistentCollection; +import org.hibernate.engine.internal.ManagedTypeHelper; import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.PersistentAttributeInterceptable; import org.hibernate.engine.spi.SessionImplementor; @@ -107,8 +108,8 @@ final Object processArrayOrNewCollection(Object collection, CollectionType colle return null; } else { - if ( entity instanceof PersistentAttributeInterceptable ) { - if ( ( (PersistentAttributeInterceptable) entity ).$$_hibernate_getInterceptor() instanceof EnhancementAsProxyLazinessInterceptor ) { + if ( ManagedTypeHelper.isPersistentAttributeInterceptable( entity ) ) { + if ( ManagedTypeHelper.asPersistentAttributeInterceptable( entity ).$$_hibernate_getInterceptor() instanceof EnhancementAsProxyLazinessInterceptor ) { return null; } } 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 bd58a1aa317e..31603da59d9d 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,11 +47,8 @@ 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; } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/HqlSqlWalker.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/HqlSqlWalker.java index 4174070e6fb4..d876610cc39f 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/HqlSqlWalker.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/HqlSqlWalker.java @@ -45,6 +45,7 @@ import org.hibernate.hql.internal.ast.tree.FromElementFactory; import org.hibernate.hql.internal.ast.tree.FromReferenceNode; import org.hibernate.hql.internal.ast.tree.IdentNode; +import org.hibernate.hql.internal.ast.tree.ImpliedFromElement; import org.hibernate.hql.internal.ast.tree.IndexNode; import org.hibernate.hql.internal.ast.tree.InsertStatement; import org.hibernate.hql.internal.ast.tree.IntoClause; @@ -524,6 +525,16 @@ private void handleWithFragment(FromElement fromElement, AST hqlWithNode) throws } } + private boolean hasAnyForcibleNotFoundImplicitJoins; + + public void registerForcibleNotFoundImplicitJoin(ImpliedFromElement impliedJoin) { + hasAnyForcibleNotFoundImplicitJoins = true; + } + + public boolean hasAnyForcibleNotFoundImplicitJoins() { + return hasAnyForcibleNotFoundImplicitJoins; + } + private static class WithClauseVisitor implements NodeTraverser.VisitationStrategy { private final FromElement joinFragment; private final QueryTranslatorImpl queryTranslatorImpl; @@ -724,7 +735,7 @@ protected boolean isNonQualifiedPropertyRef(AST ident) { final FromElement fromElement = (FromElement) fromElements.get( 0 ); try { LOG.tracev( "Attempting to resolve property [{0}] as a non-qualified ref", identText ); - return fromElement.getPropertyMapping( identText ).toType( identText ) != null; + return fromElement.isNonQualifiedPropertyRef( identText ); } catch (QueryException e) { // Should mean that no such property was found @@ -1505,7 +1516,7 @@ public Set getTreatAsDeclarationsByPath(String path) { } public Dialect getDialect() { - return sessionFactoryHelper.getFactory().getServiceRegistry().getService( JdbcServices.class ).getDialect(); + return sessionFactoryHelper.getFactory().getFastSessionServices().dialect; } public static void panic() { 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 acf4146b1fcc..7b038ccc7bb0 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 7400d42e859c..191bf7cd9fb1 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 @@ -18,7 +18,6 @@ import org.hibernate.internal.log.DeprecationLogger; import org.hibernate.internal.util.StringHelper; import org.hibernate.loader.plan.spi.EntityQuerySpace; -import org.hibernate.loader.plan.spi.QuerySpace; import org.hibernate.persister.collection.QueryableCollection; import org.hibernate.persister.entity.AbstractEntityPersister; import org.hibernate.persister.entity.EntityPersister; @@ -264,7 +263,7 @@ private void initText() { } private Type prepareLhs() throws SemanticException { - FromReferenceNode lhs = getLhs(); + final FromReferenceNode lhs = getLhs(); lhs.prepareForDot( propertyName ); return getDataType(); } @@ -362,14 +361,14 @@ private void dereferenceCollection( } private void dereferenceEntity( - EntityType entityType, + EntityType toOneType, boolean implicitJoin, String classAlias, boolean generateJoin, AST parent, AST parentPredicate) throws SemanticException { checkForCorrelatedSubquery( "dereferenceEntity" ); - // three general cases we check here as to whether to render a physical SQL join: + // three general cases we check whether to render a physical SQL join: // 1) is our parent a DotNode as well? If so, our property reference is // being further de-referenced... // 2) is this a DML statement @@ -394,15 +393,24 @@ 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 association is non-nullable and - // 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 && ( - entityType.isNullable() || - !isPropertyEmbeddedInJoinProperties( parentAsDotNode.propertyName ) - ); + + if ( generateJoin ) { + if ( implicitJoin && ( toOneType.hasNotFoundAction() || toOneType.isNullable() ) ) { + joinIsNeeded = true; + } + else { + joinIsNeeded = !isPropertyEmbeddedInJoinProperties( parentAsDotNode.propertyName ); + } + } + else { + joinIsNeeded = false; + } } 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 @@ -422,7 +430,7 @@ else if ( parentPredicate != null ) { } if ( joinIsNeeded ) { - dereferenceEntityJoin( classAlias, entityType, implicitJoin, parent ); + dereferenceEntityJoin( classAlias, toOneType, implicitJoin, parent ); } else { dereferenceEntityIdentifier( property, parentAsDotNode ); @@ -434,8 +442,11 @@ private static boolean isDotNode(AST n) { return n != null && n.getType() == SqlTokenTypes.DOT; } - private void dereferenceEntityJoin(String classAlias, EntityType propertyType, boolean impliedJoin, AST parent) - throws SemanticException { + private void dereferenceEntityJoin( + String classAlias, + EntityType toOneType, + boolean isImpliedJoin, + AST parent) throws SemanticException { dereferenceType = DereferenceType.ENTITY; if ( LOG.isDebugEnabled() ) { LOG.debugf( @@ -447,17 +458,17 @@ private void dereferenceEntityJoin(String classAlias, EntityType propertyType, b ); } // Create a new FROM node for the referenced class. - String associatedEntityName = propertyType.getAssociatedEntityName(); - String tableAlias = getAliasGenerator().createName( associatedEntityName ); + final String associatedEntityName = toOneType.getAssociatedEntityName(); + final String tableAlias = getAliasGenerator().createName( associatedEntityName ); - String[] joinColumns = getColumns(); - String joinPath = getPath(); + final String[] joinColumns = getColumns(); + final String joinPath = getPath(); - if ( impliedJoin && getWalker().isInFrom() ) { + if ( isImpliedJoin && getWalker().isInFrom() ) { joinType = getWalker().getImpliedJoinType(); } - FromClause currentFromClause = getWalker().getCurrentFromClause(); + final FromClause currentFromClause = getWalker().getCurrentFromClause(); FromElement elem = currentFromClause.findJoinByPath( joinPath ); /////////////////////////////////////////////////////////////////////////////// @@ -487,9 +498,9 @@ private void dereferenceEntityJoin(String classAlias, EntityType propertyType, b // /////////////////////////////////////////////////////////////////////////////// - boolean found = elem != null; + final boolean found = elem != null; // even though we might find a pre-existing element by join path, we may not be able to reuse it... - boolean useFoundFromElement = found && canReuse( classAlias, elem ); + final boolean useFoundFromElement = found && canReuse( classAlias, elem ); if ( !useFoundFromElement ) { // If the lhs of the join is a "component join", we need to go back to the @@ -503,36 +514,36 @@ private void dereferenceEntityJoin(String classAlias, EntityType propertyType, b throw new QueryException( "Unable to locate appropriate lhs" ); } - String role = lhsFromElement.getClassName() + "." + propertyName; + final String role = lhsFromElement.getClassName() + "." + propertyName; - JoinSequence joinSequence; + final JoinSequence joinSequence; 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(); + final String lhsTableAlias = getLhs().getFromElement().getTableAlias(); - AbstractEntityPersister persister = (AbstractEntityPersister) lhsFromElement.getEntityPersister(); + final AbstractEntityPersister persister = (AbstractEntityPersister) lhsFromElement.getEntityPersister(); - String[][] polyJoinColumns = persister.getPolymorphicJoinColumns(lhsTableAlias, propertyPath); + final String[][] polyJoinColumns = persister.getPolymorphicJoinColumns(lhsTableAlias, propertyPath); // Special join sequence that uses the poly join columns joinSequence = getSessionFactoryHelper() - .createJoinSequence( impliedJoin, propertyType, tableAlias, joinType, polyJoinColumns ); + .createJoinSequence( isImpliedJoin, toOneType, tableAlias, joinType, polyJoinColumns ); } else { // If this is an implied join in a from element, then use the implied join type which is part of the // tree parser's state (set by the grammar actions). joinSequence = getSessionFactoryHelper() - .createJoinSequence( impliedJoin, propertyType, tableAlias, joinType, joinColumns ); + .createJoinSequence( isImpliedJoin, toOneType, tableAlias, joinType, joinColumns ); } - FromElementFactory factory = new FromElementFactory( + final FromElementFactory factory = new FromElementFactory( currentFromClause, lhsFromElement, joinPath, classAlias, joinColumns, - impliedJoin + isImpliedJoin ); elem = factory.createEntityJoin( associatedEntityName, @@ -540,15 +551,32 @@ private void dereferenceEntityJoin(String classAlias, EntityType propertyType, b joinSequence, fetch, getWalker().isInFrom(), - propertyType, + toOneType, role, joinPath ); + + if ( isImpliedJoin + && toOneType.hasNotFoundAction() + && !getWalker().isSubQuery() ) { + assert elem instanceof ImpliedFromElement; + // we want to fetch this association if + // 1. its left-hand side is part of the result-graph, and + // 2. it is not already fetched + // + // unfortunately we will not know this information until later when we handle the + // select-clause - see `SelectClause#initializeExplicitSelectClause` and + // `SelectClause#initializeDerivedSelectClause`. For now, simply mark them + // and we will use that well initializing the SelectClause + final ImpliedFromElement impliedJoin = (ImpliedFromElement) elem; + impliedJoin.forceNotFoundFetch(); + } } else { // NOTE : addDuplicateAlias() already performs nullness checks on the alias. currentFromClause.addDuplicateAlias( classAlias, elem ); } + setImpliedJoin( elem ); getWalker().addQuerySpaces( elem.getEntityPersister().getQuerySpaces() ); setFromElement( elem ); // This 'dot' expression now refers to the resulting from element. 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 000000000000..9d036aa49787 --- /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.getElementType().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 969c1507ba4d..179d542a0c40 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 @@ -471,6 +471,10 @@ public FromElement getFetchOrigin() { public static final String DISCRIMINATOR_PROPERTY_NAME = "class"; private TypeDiscriminatorMetadata typeDiscriminatorMetadata; + public boolean isNonQualifiedPropertyRef(String identifier) { + return elementType.isNonQualifiedPropertyRef( identifier ); + } + private static class TypeDiscriminatorMetadataImpl implements TypeDiscriminatorMetadata { private final DiscriminatorMetadata persisterDiscriminatorMetadata; private final String alias; 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 23e8ce656f13..24e7801217de 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 @@ -660,6 +660,27 @@ public String[] toColumns(final String tableAlias) { }; } + /** + * Does the incoming identifier represent a non-qualified attribute reference. + * + * E.g. `... from Order where total > :discountThreshold`. We are checking + * the identifier `total` and see it is an attribute of `Order`, so it is in fact + * an unqualified reference to that attribute + */ + public boolean isNonQualifiedPropertyRef(String identifier) { + if ( queryableCollection == null ) { + assert persister != null; + try { + return persister.getPropertyType( identifier ) != null; + } + catch (QueryException qe) { + return false; + } + } + + return false; + } + private class SpecialManyToManyCollectionPropertyMapping implements PropertyMapping { @Override public Type getType() { 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 32b95144b637..ab5c9af1ddfe 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 @@ -39,6 +39,7 @@ private static enum DereferenceType { } private boolean nakedPropertyRef; + private boolean fromClauseAlias; private String[] columns; public String[] getColumns() { @@ -103,82 +104,85 @@ private void initText(String[] columns) { } public void resolve(boolean generateJoin, boolean implicitJoin, String classAlias, AST parent, AST parentPredicate) { - if (!isResolved()) { - if ( getWalker().getCurrentFromClause().isFromElementAlias( getText() ) ) { - FromElement fromElement = getWalker().getCurrentFromClause().getFromElement( getText() ); - if ( fromElement.getQueryableCollection() != null && fromElement.getQueryableCollection().getElementType().isComponentType() ) { - if ( getWalker().isInSelect() ) { - // This is a reference to an element collection - setFromElement( fromElement ); - super.setDataType( fromElement.getQueryableCollection().getElementType() ); - this.columns = resolveColumns( fromElement.getQueryableCollection() ); - initText( getColumns() ); - setFirstChild( null ); - // Don't resolve it - } - else { - resolveAsAlias(); - // Don't resolve it - } + if ( isResolved() ) { + return; + } + + if ( getWalker().getCurrentFromClause().isFromElementAlias( getText() ) ) { + final FromElement fromElement = getWalker().getCurrentFromClause().getFromElement( getText() ); + if ( fromElement.getQueryableCollection() != null && fromElement.getQueryableCollection().getElementType().isComponentType() ) { + if ( getWalker().isInSelect() ) { + // This is a reference to an element collection + setFromElement( fromElement ); + super.setDataType( fromElement.getQueryableCollection().getElementType() ); + this.columns = resolveColumns( fromElement.getQueryableCollection() ); + initText( getColumns() ); + setFirstChild( null ); + // Don't resolve it } - else if ( resolveAsAlias() ) { - setResolved(); - // We represent a from-clause alias + else { + resolveAsAlias(); + // Don't resolve it } } - else if ( - getColumns() != null - && ( getWalker().getAST() instanceof AbstractMapComponentNode || getWalker().getAST() instanceof IndexNode ) - && getWalker().getCurrentFromClause().isFromElementAlias( getOriginalText() ) - ) { - // We might have to revert our decision that this is naked element collection reference when we encounter it is embedded in a map function - setText( getOriginalText() ); - if ( resolveAsAlias() ) { - setResolved(); - } + else if ( resolveAsAlias() ) { + setResolved(); + // We represent a from-clause alias } - else if (parent != null && parent.getType() == SqlTokenTypes.DOT) { - DotNode dot = (DotNode) parent; - if (parent.getFirstChild() == this) { - if (resolveAsNakedComponentPropertyRefLHS(dot)) { - // we are the LHS of the DOT representing a naked comp-prop-ref - setResolved(); - } - } - else { - if (resolveAsNakedComponentPropertyRefRHS(dot)) { - // we are the RHS of the DOT representing a naked comp-prop-ref - setResolved(); - } + } + else if ( + getColumns() != null + && ( getWalker().getAST() instanceof AbstractMapComponentNode || getWalker().getAST() instanceof IndexNode ) + && getWalker().getCurrentFromClause().isFromElementAlias( getOriginalText() ) + ) { + // We might have to revert our decision that this is naked element collection reference when we encounter it is embedded in a map function + setText( getOriginalText() ); + if ( resolveAsAlias() ) { + setResolved(); + } + } + else if (parent != null && parent.getType() == SqlTokenTypes.DOT) { + DotNode dot = (DotNode) parent; + if (parent.getFirstChild() == this) { + if (resolveAsNakedComponentPropertyRefLHS(dot)) { + // we are the LHS of the DOT representing a naked comp-prop-ref + setResolved(); } } else { - DereferenceType result = resolveAsNakedPropertyRef(); - if (result == DereferenceType.PROPERTY_REF) { - // we represent a naked (simple) prop-ref + if (resolveAsNakedComponentPropertyRefRHS(dot)) { + // we are the RHS of the DOT representing a naked comp-prop-ref setResolved(); } - else if (result == DereferenceType.COMPONENT_REF) { - // EARLY EXIT!!! return so the resolve call explicitly coming from DotNode can - // resolve this... - return; - } } + } + else { + DereferenceType result = resolveAsNakedPropertyRef(); + if (result == DereferenceType.PROPERTY_REF) { + // we represent a naked (simple) prop-ref + setResolved(); + } + else if (result == DereferenceType.COMPONENT_REF) { + // EARLY EXIT!!! return so the resolve call explicitly coming from DotNode can + // resolve this... + return; + } + } - // if we are still not resolved, we might represent a constant. - // needed to add this here because the allowance of - // naked-prop-refs in the grammar collides with the - // definition of literals/constants ("nondeterminism"). - // TODO: cleanup the grammar so that "processConstants" is always just handled from here - if (!isResolved()) { - try { - getWalker().getLiteralProcessor().processConstant(this, false); - } - catch (Throwable ignore) { - // just ignore it for now, it'll get resolved later... - } + // if we are still not resolved, we might represent a constant. + // needed to add this here because the allowance of + // naked-prop-refs in the grammar collides with the + // definition of literals/constants ("nondeterminism"). + // TODO: cleanup the grammar so that "processConstants" is always just handled from here + if (!isResolved()) { + try { + getWalker().getLiteralProcessor().processConstant(this, false); + } + catch (Throwable ignore) { + // just ignore it for now, it'll get resolved later... } } + } private boolean resolveAsAlias() { diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/ImpliedFromElement.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/ImpliedFromElement.java index ccb3b240cb61..583c3a663d1c 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/ImpliedFromElement.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/ImpliedFromElement.java @@ -23,6 +23,8 @@ public class ImpliedFromElement extends FromElement { */ private boolean inProjectionList; + private boolean forcedNotFoundFetch; + /** * Here to add debug breakpoints */ @@ -31,6 +33,15 @@ public ImpliedFromElement() { super(); } + public void forceNotFoundFetch() { + getWalker().registerForcibleNotFoundImplicitJoin( this ); + forcedNotFoundFetch = true; + } + + public boolean isForcedNotFoundFetch() { + return forcedNotFoundFetch; + } + public boolean isImplied() { return true; } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/SelectClause.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/SelectClause.java index 6d4dfb7d2359..4e159dee1cf4 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/SelectClause.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/SelectClause.java @@ -15,7 +15,6 @@ import org.hibernate.hql.internal.antlr.SqlTokenTypes; import org.hibernate.hql.internal.ast.util.ASTAppender; import org.hibernate.hql.internal.ast.util.ASTIterator; -import org.hibernate.hql.internal.ast.util.ASTPrinter; import org.hibernate.hql.internal.ast.util.TokenPrinters; import org.hibernate.type.Type; @@ -108,7 +107,7 @@ public AggregatedSelectExpression getAggregatedSelectExpression() { /** * Prepares an explicitly defined select clause. * - * @param fromClause The from clause linked to this select clause. + * @param fromClause The from-clause linked to this select clause. * * @throws SemanticException indicates a semantic issue with the explicit select clause. */ @@ -133,8 +132,24 @@ public void initializeExplicitSelectClause(FromClause fromClause) throws Semanti ); } + if ( !getWalker().isShallowQuery() ) { + if ( getWalker().hasAnyForcibleNotFoundImplicitJoins() ) { + // we encountered implicit joins to at least one NotFound association mapping. + // find them and make sure they get added to the result-graph if their parent is + for ( SelectExpression selectExpression : selectExpressions ) { + if ( selectExpression instanceof FromReferenceNode ) { + final FromReferenceNode selectedPath = (FromReferenceNode) selectExpression; + if ( isFromElementSelection( selectedPath ) ) { + final FromElement fromElement = selectedPath.getFromElement(); + applyForcibleImplicitNotFoundJoins( fromElement ); + } + } + } + } + } + for ( SelectExpression selectExpression : selectExpressions ) { - if ( AggregatedSelectExpression.class.isInstance( selectExpression ) ) { + if ( selectExpression instanceof AggregatedSelectExpression ) { aggregatedSelectExpression = (AggregatedSelectExpression) selectExpression; queryReturnTypeList.addAll( aggregatedSelectExpression.getAggregatedSelectionTypeList() ); scalarSelect = true; @@ -253,6 +268,31 @@ public void initializeExplicitSelectClause(FromClause fromClause) throws Semanti finishInitialization( /*sqlResultTypeList,*/ queryReturnTypeList ); } + private boolean isFromElementSelection(FromReferenceNode selectedPath) { + if ( selectedPath.getType() == HqlSqlTokenTypes.ALIAS_REF ) { + return true; + } + + // ugh + return selectedPath instanceof SelectExpressionImpl; + } + + private void applyForcibleImplicitNotFoundJoins(FromElement fromElement) { + final List destinations = fromElement.getDestinations(); + for ( int i = 0; i < destinations.size(); i++ ) { + final FromElement destination = destinations.get( i ); + if ( destination instanceof ImpliedFromElement ) { + final ImpliedFromElement impliedJoin = (ImpliedFromElement) destination; + if ( impliedJoin.isForcedNotFoundFetch() ) { + impliedJoin.setInProjectionList( true ); + impliedJoin.setFetch( true ); + } + } + + applyForcibleImplicitNotFoundJoins( destination ); + } + } + private void finishInitialization(ArrayList queryReturnTypeList) { queryReturnTypes = (Type[]) queryReturnTypeList.toArray( new Type[queryReturnTypeList.size()] ); initializeColumnNames(); diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/SessionFactoryHelper.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/SessionFactoryHelper.java index 355632ae98d6..ef0e32d3cf87 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/SessionFactoryHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/SessionFactoryHelper.java @@ -28,6 +28,7 @@ import org.hibernate.type.AssociationType; import org.hibernate.type.CollectionType; import org.hibernate.type.EntityType; +import org.hibernate.type.ManyToOneType; import org.hibernate.type.Type; import antlr.SemanticException; diff --git a/hibernate-core/src/main/java/org/hibernate/hql/spi/id/cte/AbstractCteValuesListBulkIdHandler.java b/hibernate-core/src/main/java/org/hibernate/hql/spi/id/cte/AbstractCteValuesListBulkIdHandler.java index c8c118cf15a6..732b07087862 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/spi/id/cte/AbstractCteValuesListBulkIdHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/spi/id/cte/AbstractCteValuesListBulkIdHandler.java @@ -72,7 +72,7 @@ protected String determineIdTableName(Queryable persister) { "HT_" + StringHelper.unquote( persister.getTableName(), jdbcEnvironment.getDialect() ) ).render(); - return persister.getFactory().getSqlStringGenerationContext().format( + return persister.getFactory().getSqlStringGenerationContext().formatWithoutDefaults( new QualifiedTableName( Identifier.toIdentifier( catalog ), Identifier.toIdentifier( schema ), diff --git a/hibernate-core/src/main/java/org/hibernate/hql/spi/id/global/GlobalTemporaryTableBulkIdStrategy.java b/hibernate-core/src/main/java/org/hibernate/hql/spi/id/global/GlobalTemporaryTableBulkIdStrategy.java index 17dbd9de3107..b9995532786a 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/spi/id/global/GlobalTemporaryTableBulkIdStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/spi/id/global/GlobalTemporaryTableBulkIdStrategy.java @@ -112,7 +112,7 @@ protected IdTableInfoImpl buildIdTableInfo( context.dropStatements.add( buildIdTableDropStatement( idTable, sqlStringGenerationContext ) ); } - final String renderedName = sqlStringGenerationContext.format( idTable.getQualifiedTableName() ); + final String renderedName = sqlStringGenerationContext.formatWithoutDefaults( idTable.getQualifiedTableName() ); return new IdTableInfoImpl( renderedName ); } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/spi/id/local/LocalTemporaryTableBulkIdStrategy.java b/hibernate-core/src/main/java/org/hibernate/hql/spi/id/local/LocalTemporaryTableBulkIdStrategy.java index 715cb7fc9361..e8f936e2d53a 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/spi/id/local/LocalTemporaryTableBulkIdStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/spi/id/local/LocalTemporaryTableBulkIdStrategy.java @@ -119,7 +119,7 @@ protected IdTableInfoImpl buildIdTableInfo( context.dropStatements.add( dropStatement ); } return new IdTableInfoImpl( - sqlStringGenerationContext.format( idTable.getQualifiedTableName() ), + sqlStringGenerationContext.formatWithoutDefaults( idTable.getQualifiedTableName() ), buildIdTableCreateStatement( idTable, metadata, sqlStringGenerationContext ), dropStatement ); diff --git a/hibernate-core/src/main/java/org/hibernate/hql/spi/id/persistent/PersistentTableBulkIdStrategy.java b/hibernate-core/src/main/java/org/hibernate/hql/spi/id/persistent/PersistentTableBulkIdStrategy.java index dec96832e55d..98a3c7bd4d56 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/spi/id/persistent/PersistentTableBulkIdStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/spi/id/persistent/PersistentTableBulkIdStrategy.java @@ -127,7 +127,7 @@ protected IdTableInfoImpl buildIdTableInfo( MetadataImplementor metadata, PreparationContextImpl context, SqlStringGenerationContext sqlStringGenerationContext) { - final String renderedName = sqlStringGenerationContext.format( idTable.getQualifiedTableName() ); + final String renderedName = sqlStringGenerationContext.formatWithoutDefaults( idTable.getQualifiedTableName() ); context.creationStatements.add( buildIdTableCreateStatement( idTable, metadata, sqlStringGenerationContext diff --git a/hibernate-core/src/main/java/org/hibernate/id/BulkInsertionCapableIdentifierGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/BulkInsertionCapableIdentifierGenerator.java index 371b8d0f7735..21c592d834d8 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/BulkInsertionCapableIdentifierGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/BulkInsertionCapableIdentifierGenerator.java @@ -7,6 +7,7 @@ package org.hibernate.id; import org.hibernate.boot.model.relational.SqlStringGenerationContext; +import org.hibernate.dialect.Dialect; /** * Specialized contract for {@link IdentifierGenerator} implementations capable of being used in conjunction @@ -32,5 +33,18 @@ public interface BulkInsertionCapableIdentifierGenerator extends IdentifierGener * * @return The identifier value generation fragment (SQL). {@code null} indicates that no fragment is needed. */ - public String determineBulkInsertionIdentifierGenerationSelectFragment(SqlStringGenerationContext context); + default String determineBulkInsertionIdentifierGenerationSelectFragment(Dialect dialect) { + throw new IllegalStateException("determineBulkInsertionIdentifierGenerationSelectFragment(...) was not implemented!"); + } + + /** + * Return the select expression fragment, if any, that generates the identifier values. + * + * @param context A context for SQL string generation. + * + * @return The identifier value generation fragment (SQL). {@code null} indicates that no fragment is needed. + */ + default String determineBulkInsertionIdentifierGenerationSelectFragment(SqlStringGenerationContext context) { + return determineBulkInsertionIdentifierGenerationSelectFragment( context.getDialect() ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/id/CompositeNestedGeneratedValueGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/CompositeNestedGeneratedValueGenerator.java index dd730989aea5..5ee2a9eaa94a 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/CompositeNestedGeneratedValueGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/CompositeNestedGeneratedValueGenerator.java @@ -79,7 +79,8 @@ public interface GenerationPlan extends ExportableProducer { * * @param context A context to help generate SQL strings */ - void initialize(SqlStringGenerationContext context); + default void initialize(SqlStringGenerationContext context) { + } /** * Execute the value generation. diff --git a/hibernate-core/src/main/java/org/hibernate/id/MultipleHiLoPerTableGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/MultipleHiLoPerTableGenerator.java index ea617988cfdb..bc077ec9415b 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/MultipleHiLoPerTableGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/MultipleHiLoPerTableGenerator.java @@ -22,6 +22,7 @@ import org.hibernate.boot.model.relational.QualifiedName; import org.hibernate.boot.model.relational.QualifiedNameParser; import org.hibernate.boot.model.relational.SqlStringGenerationContext; +import org.hibernate.dialect.Dialect; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.engine.jdbc.internal.FormatStyle; import org.hibernate.engine.jdbc.spi.JdbcServices; @@ -93,6 +94,8 @@ public class MultipleHiLoPerTableGenerator implements PersistentIdentifierGenera private QualifiedName qualifiedTableName; private QualifiedName physicalTableName; + @Deprecated + private String formattedTableNameForLegacyGetter; private String segmentColumnName; private String segmentName; private String valueColumnName; @@ -343,6 +346,13 @@ public void registerExportables(Database database) { // allow physical naming strategies a chance to kick in physicalTableName = table.getQualifiedTableName(); + + final JdbcEnvironment jdbcEnvironment = database.getJdbcEnvironment(); + final Dialect dialect = jdbcEnvironment.getDialect(); + this.formattedTableNameForLegacyGetter = jdbcEnvironment.getQualifiedObjectNameFormatter().format( + physicalTableName, + dialect + ); } @Override @@ -376,4 +386,8 @@ public void initialize(SqlStringGenerationContext context) { } + @Deprecated + public Object generatorKey() { + return formattedTableNameForLegacyGetter; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/id/PersistentIdentifierGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/PersistentIdentifierGenerator.java index 4a129711e4ab..5798d717a57a 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/PersistentIdentifierGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/PersistentIdentifierGenerator.java @@ -58,4 +58,16 @@ public interface PersistentIdentifierGenerator extends IdentifierGenerator { * The key under which to find the {@link org.hibernate.boot.model.naming.ObjectNameNormalizer} in the config param map. */ String IDENTIFIER_NORMALIZER = "identifier_normalizer"; + + /** + * Return a key unique to the underlying database objects. Prevents us from + * trying to create/remove them multiple times. + * + * @return Object an identifying key for this generator + * @deprecated No longer necessary. + */ + @Deprecated + default Object generatorKey() { + return null; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/id/SequenceGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/SequenceGenerator.java index d480034c5dcd..20742e057868 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/SequenceGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/SequenceGenerator.java @@ -20,6 +20,7 @@ import org.hibernate.boot.model.relational.QualifiedNameParser; import org.hibernate.boot.model.relational.Sequence; import org.hibernate.boot.model.relational.SqlStringGenerationContext; +import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.log.DeprecationLogger; import org.hibernate.internal.util.config.ConfigurationHelper; @@ -64,6 +65,8 @@ public class SequenceGenerator private QualifiedName logicalQualifiedSequenceName; private QualifiedName physicalSequenceName; + @Deprecated + private String formattedSequenceNameForLegacyGetter; private Type identifierType; private String sql; @@ -71,6 +74,17 @@ protected Type getIdentifierType() { return identifierType; } + @Override + @Deprecated + public Object generatorKey() { + return getSequenceName(); + } + + @Deprecated + public String getSequenceName() { + return formattedSequenceNameForLegacyGetter; + } + public QualifiedName getPhysicalSequenceName() { return physicalSequenceName; } @@ -174,6 +188,10 @@ public void registerExportables(Database database) { ); } this.physicalSequenceName = sequence.getName(); + + final JdbcEnvironment jdbcEnvironment = database.getJdbcEnvironment(); + this.formattedSequenceNameForLegacyGetter = jdbcEnvironment.getQualifiedObjectNameFormatter() + .format( physicalSequenceName, jdbcEnvironment.getDialect() ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/id/enhanced/DatabaseStructure.java b/hibernate-core/src/main/java/org/hibernate/id/enhanced/DatabaseStructure.java index 80b493ed5e8c..4079578a188d 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/enhanced/DatabaseStructure.java +++ b/hibernate-core/src/main/java/org/hibernate/id/enhanced/DatabaseStructure.java @@ -19,6 +19,17 @@ * @author Steve Ebersole */ public interface DatabaseStructure extends ExportableProducer { + + /** + * The name of the database structure (table or sequence). + * @deprecated Use {@link #getPhysicalName()} instead. + */ + @Deprecated + default String getName() { + // Not a great implementation, but that'll have to do: it's only for backwards compatibility. + return getPhysicalName().render(); + } + /** * The physical name of the database structure (table or sequence). *

    diff --git a/hibernate-core/src/main/java/org/hibernate/id/enhanced/PooledOptimizer.java b/hibernate-core/src/main/java/org/hibernate/id/enhanced/PooledOptimizer.java index 59e90f1cc30d..1531b5c5e914 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/enhanced/PooledOptimizer.java +++ b/hibernate-core/src/main/java/org/hibernate/id/enhanced/PooledOptimizer.java @@ -70,22 +70,21 @@ public synchronized Serializable generate(AccessCallback callback) { final GenerationState generationState = locateGenerationState( callback.getTenantIdentifier() ); if ( generationState.hiValue == null ) { - generationState.value = callback.getNextValue(); + generationState.hiValue = callback.getNextValue(); // unfortunately not really safe to normalize this // to 1 as an initial value like we do for the others // because we would not be able to control this if // we are using a sequence... - if ( generationState.value.lt( 1 ) ) { - log.pooledOptimizerReportedInitialValue( generationState.value ); + if ( generationState.hiValue.lt( 1 ) ) { + log.pooledOptimizerReportedInitialValue( generationState.hiValue ); } // the call to obtain next-value just gave us the initialValue if ( ( initialValue == -1 - && generationState.value.lt( incrementSize ) ) - || generationState.value.eq( initialValue ) ) { - generationState.hiValue = callback.getNextValue(); + && generationState.hiValue.lt( incrementSize ) ) + || generationState.hiValue.eq( initialValue ) ) { + generationState.value = generationState.hiValue.copy(); } else { - generationState.hiValue = generationState.value; generationState.value = generationState.hiValue.copy().subtract( incrementSize - 1 ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/id/enhanced/SequenceStructure.java b/hibernate-core/src/main/java/org/hibernate/id/enhanced/SequenceStructure.java index fe3051fae5b9..06d86cfe3d88 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/enhanced/SequenceStructure.java +++ b/hibernate-core/src/main/java/org/hibernate/id/enhanced/SequenceStructure.java @@ -43,6 +43,8 @@ public class SequenceStructure implements DatabaseStructure { private String sql; private boolean applyIncrementSizeToSourceValues; private int accessCounter; + @Deprecated + private String formattedSequenceNameForLegacyGetter; protected QualifiedName physicalSequenceName; public SequenceStructure( @@ -58,6 +60,12 @@ public SequenceStructure( this.numberType = numberType; } + @Override + @Deprecated + public String getName() { + return formattedSequenceNameForLegacyGetter; + } + @Override public QualifiedName getPhysicalName() { return physicalSequenceName; @@ -181,5 +189,9 @@ protected void buildSequence(Database database) { } this.physicalSequenceName = sequence.getName(); + + final JdbcEnvironment jdbcEnvironment = database.getJdbcEnvironment(); + this.formattedSequenceNameForLegacyGetter = jdbcEnvironment.getQualifiedObjectNameFormatter() + .format( physicalSequenceName, jdbcEnvironment.getDialect() ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/id/enhanced/SequenceStyleGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/enhanced/SequenceStyleGenerator.java index 5f15d73fad06..40ffdb47a2ae 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/enhanced/SequenceStyleGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/enhanced/SequenceStyleGenerator.java @@ -534,6 +534,16 @@ public Serializable generate(SharedSessionContractImplementor session, Object ob return optimizer.generate( databaseStructure.buildCallback( session ) ); } + + // PersistentIdentifierGenerator implementation ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + @Override + @Deprecated + public Object generatorKey() { + return databaseStructure.getName(); + } + + // BulkInsertionCapableIdentifierGenerator implementation ~~~~~~~~~~~~~~~~~ @Override diff --git a/hibernate-core/src/main/java/org/hibernate/id/enhanced/TableGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/enhanced/TableGenerator.java index 250d7c26d635..4747c4c9245a 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/enhanced/TableGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/enhanced/TableGenerator.java @@ -249,6 +249,12 @@ public class TableGenerator implements PersistentIdentifierGenerator { private Optimizer optimizer; private long accessCount; + @Override + @Deprecated + public Object generatorKey() { + return qualifiedTableName.render(); + } + /** * Type mapping for the identifier. * diff --git a/hibernate-core/src/main/java/org/hibernate/id/enhanced/TableStructure.java b/hibernate-core/src/main/java/org/hibernate/id/enhanced/TableStructure.java index e9924fe01708..5d1ddfc12dd4 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/enhanced/TableStructure.java +++ b/hibernate-core/src/main/java/org/hibernate/id/enhanced/TableStructure.java @@ -55,6 +55,8 @@ public class TableStructure implements DatabaseStructure { private final Class numberType; private QualifiedName physicalTableName; + @Deprecated + private String formattedTableNameForLegacyGetter; private String valueColumnNameText; private String selectQuery; @@ -78,6 +80,12 @@ public TableStructure( this.numberType = numberType; } + @Override + @Deprecated + public String getName() { + return formattedTableNameForLegacyGetter; + } + @Override public QualifiedName getPhysicalName() { return physicalTableName; @@ -247,6 +255,9 @@ public void registerExportables(Database database) { } this.physicalTableName = table.getQualifiedTableName(); + this.formattedTableNameForLegacyGetter = jdbcEnvironment.getQualifiedObjectNameFormatter() + .format( physicalTableName, dialect ); + valueColumnNameText = logicalValueColumnNameIdentifier.render( dialect ); if ( tableCreated ) { ExportableColumn valueColumn = new ExportableColumn( diff --git a/hibernate-core/src/main/java/org/hibernate/id/insert/InsertGeneratedIdentifierDelegate.java b/hibernate-core/src/main/java/org/hibernate/id/insert/InsertGeneratedIdentifierDelegate.java index 564a7cad92f3..826f0d3ffe23 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/insert/InsertGeneratedIdentifierDelegate.java +++ b/hibernate-core/src/main/java/org/hibernate/id/insert/InsertGeneratedIdentifierDelegate.java @@ -22,6 +22,18 @@ */ public interface InsertGeneratedIdentifierDelegate { + /** + * Build a {@link org.hibernate.sql.Insert} specific to the delegate's mode + * of handling generated key values. + * + * @return The insert object. + * @deprecated Implement {@link #prepareIdentifierGeneratingInsert(SqlStringGenerationContext)} instead. + */ + @Deprecated + default IdentifierGeneratingInsert prepareIdentifierGeneratingInsert() { + throw new IllegalStateException("prepareIdentifierGeneratingInsert(...) was not implemented!"); + } + /** * Build a {@link org.hibernate.sql.Insert} specific to the delegate's mode * of handling generated key values. @@ -29,7 +41,9 @@ public interface InsertGeneratedIdentifierDelegate { * @param context A context to help generate SQL strings * @return The insert object. */ - IdentifierGeneratingInsert prepareIdentifierGeneratingInsert(SqlStringGenerationContext context); + default IdentifierGeneratingInsert prepareIdentifierGeneratingInsert(SqlStringGenerationContext context) { + return prepareIdentifierGeneratingInsert(); + } /** * Perform the indicated insert SQL statement and determine the identifier value diff --git a/hibernate-core/src/main/java/org/hibernate/id/insert/InsertSelectIdentityInsert.java b/hibernate-core/src/main/java/org/hibernate/id/insert/InsertSelectIdentityInsert.java index e130e197048d..d97c91991468 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/insert/InsertSelectIdentityInsert.java +++ b/hibernate-core/src/main/java/org/hibernate/id/insert/InsertSelectIdentityInsert.java @@ -6,6 +6,7 @@ */ package org.hibernate.id.insert; import org.hibernate.dialect.Dialect; +import org.hibernate.sql.Insert; /** * Specialized IdentifierGeneratingInsert which appends the database @@ -15,11 +16,18 @@ * @author Steve Ebersole */ public class InsertSelectIdentityInsert extends IdentifierGeneratingInsert { + protected String identityColumnName; + + public Insert addIdentityColumn(String columnName) { + identityColumnName = columnName; + return super.addIdentityColumn( columnName ); + } + public InsertSelectIdentityInsert(Dialect dialect) { super( dialect ); } public String toStatementString() { - return getDialect().getIdentityColumnSupport().appendIdentitySelectToInsert( super.toStatementString() ); + return getDialect().getIdentityColumnSupport().appendIdentitySelectToInsert( identityColumnName, super.toStatementString() ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java b/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java index bef97b2fecf6..1c5ab2a65012 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java @@ -60,6 +60,7 @@ import org.hibernate.event.spi.ReplicateEventListener; import org.hibernate.event.spi.ResolveNaturalIdEventListener; import org.hibernate.event.spi.SaveOrUpdateEventListener; +import org.hibernate.hql.spi.QueryTranslatorFactory; import org.hibernate.internal.log.DeprecationLogger; import org.hibernate.internal.util.NullnessHelper; import org.hibernate.jpa.QueryHints; @@ -163,11 +164,14 @@ public final class FastSessionServices { final int defaultJdbcBatchSize; //Private fields: - private final Dialect dialect; private final CacheStoreMode defaultCacheStoreMode; private final CacheRetrieveMode defaultCacheRetrieveMode; private final ConnectionObserverStatsBridge defaultJdbcObservers; + //Public fields: + public final Dialect dialect; + public final QueryTranslatorFactory queryTranslatorFactory; + FastSessionServices(SessionFactoryImpl sf) { Objects.requireNonNull( sf ); final ServiceRegistryImplementor sr = sf.getServiceRegistry(); @@ -226,6 +230,7 @@ public final class FastSessionServices { this.classLoaderService = sr.getService( ClassLoaderService.class ); this.transactionCoordinatorBuilder = sr.getService( TransactionCoordinatorBuilder.class ); this.jdbcServices = sr.getService( JdbcServices.class ); + this.queryTranslatorFactory = sr.getService( QueryTranslatorFactory.class ); this.isJtaTransactionAccessible = isTransactionAccessible( sf, transactionCoordinatorBuilder ); @@ -246,12 +251,7 @@ private static FlushMode initializeDefaultFlushMode(Map defaultS () -> defaultSessionProperties.get( AvailableSettings.FLUSH_MODE ), () -> { final Object oldSetting = defaultSessionProperties.get( org.hibernate.jpa.AvailableSettings.FLUSH_MODE ); - if ( oldSetting != null ) { - DeprecationLogger.DEPRECATION_LOGGER.deprecatedSetting( - org.hibernate.jpa.AvailableSettings.FLUSH_MODE, - AvailableSettings.FLUSH_MODE - ); - } + //Not invoking the DeprecationLogger in this case as the user can't avoid using this property (the string value is the same) return oldSetting; } ); 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 6ee59ac1a42b..884c8476d300 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java @@ -341,26 +341,6 @@ public void sessionFactoryClosed(SessionFactory factory) { currentSessionContext = buildCurrentSessionContext(); - //checking for named queries - if ( settings.isNamedQueryStartupCheckingEnabled() ) { - final Map errors = checkNamedQueries(); - if ( !errors.isEmpty() ) { - StringBuilder failingQueries = new StringBuilder( "Errors in named queries: " ); - String separator = System.lineSeparator(); - - for ( Map.Entry entry : errors.entrySet() ) { - LOG.namedQueryError( entry.getKey(), entry.getValue() ); - - failingQueries - .append( separator) - .append( entry.getKey() ) - .append( " failed because of: " ) - .append( entry.getValue() ); - } - throw new HibernateException( failingQueries.toString() ); - } - } - // this needs to happen after persisters are all ready to go... this.fetchProfiles = new HashMap<>(); for ( org.hibernate.mapping.FetchProfile mappingProfile : metadata.getFetchProfiles() ) { @@ -399,6 +379,26 @@ public void sessionFactoryClosed(SessionFactory factory) { this.defaultStatelessOptions = this.defaultSessionOpenOptions == null ? null : withStatelessOptions(); this.fastSessionServices = new FastSessionServices( this ); + //checking for named queries - requires fastSessionServices to have been initialized. + if ( settings.isNamedQueryStartupCheckingEnabled() ) { + final Map errors = checkNamedQueries(); + if ( !errors.isEmpty() ) { + StringBuilder failingQueries = new StringBuilder( "Errors in named queries: " ); + String separator = System.lineSeparator(); + + for ( Map.Entry entry : errors.entrySet() ) { + LOG.namedQueryError( entry.getKey(), entry.getValue() ); + + failingQueries + .append( separator) + .append( entry.getKey() ) + .append( " failed because of: " ) + .append( entry.getValue() ); + } + throw new HibernateException( failingQueries.toString() ); + } + } + this.observer.sessionFactoryCreated( this ); SessionFactoryRegistry.INSTANCE.addSessionFactory( 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 6489891016e6..7df28680d4d3 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java @@ -42,6 +42,7 @@ import org.hibernate.CacheMode; import org.hibernate.Criteria; +import org.hibernate.FetchNotFoundException; import org.hibernate.Filter; import org.hibernate.FlushMode; import org.hibernate.HibernateException; @@ -273,12 +274,7 @@ public SessionImpl(SessionFactoryImpl factory, SessionCreationOptions options) { () -> getSessionProperty( AvailableSettings.FLUSH_MODE ), () -> { final Object oldSetting = getSessionProperty( org.hibernate.jpa.AvailableSettings.FLUSH_MODE ); - if ( oldSetting != null ) { - DeprecationLogger.DEPRECATION_LOGGER.deprecatedSetting( - org.hibernate.jpa.AvailableSettings.FLUSH_MODE, - AvailableSettings.FLUSH_MODE - ); - } + //Not invoking the DeprecationLogger in this case as the user can't avoid using this property (the string value is the same) return oldSetting; } ); @@ -1042,7 +1038,16 @@ public Object immediateLoad(String entityName, Serializable id) throws Hibernate LoadEvent event = loadEvent; loadEvent = null; event = recycleEventInstance( event, id, entityName ); - fireLoadNoChecks( event, LoadEventListener.IMMEDIATE_LOAD ); + + try { + fireLoadNoChecks( event, LoadEventListener.IMMEDIATE_LOAD ); + } + catch (FetchNotFoundException e) { + // when this happens at the "top-level" of a load, for 5.x we want to + // keep this the same user-facing exception as it was before + getSessionFactory().getEntityNotFoundDelegate().handleEntityNotFound( e.getEntityName(), (Serializable) e.getIdentifier() ); + } + Object result = event.getResult(); if ( loadEvent == null ) { event.setEntityClassName( null ); @@ -2249,6 +2254,19 @@ private void throwTransientObjectException(Object object) throws HibernateExcept ); } + @Override @SuppressWarnings("unchecked") + public T getReference(T object) { + checkOpen(); + if ( object instanceof HibernateProxy ) { + LazyInitializer initializer = ( (HibernateProxy) object ).getHibernateLazyInitializer(); + return (T) getReference( initializer.getPersistentClass(), initializer.getIdentifier() ); + } + else { + EntityPersister persister = getEntityPersister( null, object ); + return (T) getReference( persister.getMappedClass(), persister.getIdentifier(object, this) ); + } + } + @Override public String guessEntityName(Object object) throws HibernateException { checkOpenOrWaitingForAutoClose(); diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/ReflectHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/ReflectHelper.java index fd4ab26ba813..66e82fd20800 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/ReflectHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/ReflectHelper.java @@ -435,7 +435,7 @@ private static Field locateField(Class clazz, String propertyName) { } } - private static boolean isStaticField(Field field) { + public static boolean isStaticField(Field field) { return field != null && ( field.getModifiers() & Modifier.STATIC ) == Modifier.STATIC; } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/CollectionHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/CollectionHelper.java index af4936bffb7d..c58052d287d9 100755 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/CollectionHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/CollectionHelper.java @@ -220,7 +220,7 @@ public static boolean isEmpty(Object[] objects) { * The goal is to save memory. * @param set * @param - * @return + * @return will never return null, but might return an immutable collection. */ public static Set toSmallSet(Set set) { switch ( set.size() ) { 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 7d26f631be4c..aebb919bbcaf 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 @@ -12,6 +12,7 @@ import org.hibernate.Hibernate; import org.hibernate.MappingException; +import org.hibernate.engine.internal.ManagedTypeHelper; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.ManagedEntity; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -70,8 +71,8 @@ public Object getIdentifier(Object entity) { if ( entity instanceof HibernateProxy ) { return ((HibernateProxy) entity).getHibernateLazyInitializer().getInternalIdentifier(); } - else if ( entity instanceof ManagedEntity ) { - EntityEntry entityEntry = ((ManagedEntity) entity).$$_hibernate_getEntityEntry(); + else if ( ManagedTypeHelper.isManagedEntity( entity ) ) { + EntityEntry entityEntry = ManagedTypeHelper.asManagedEntity( entity ).$$_hibernate_getEntityEntry(); if ( entityEntry != null ) { return entityEntry.getId(); } diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/internal/util/PersistenceUtilHelper.java b/hibernate-core/src/main/java/org/hibernate/jpa/internal/util/PersistenceUtilHelper.java index a7e6415840b1..71fd9b4cb4ba 100644 --- a/hibernate-core/src/main/java/org/hibernate/jpa/internal/util/PersistenceUtilHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/jpa/internal/util/PersistenceUtilHelper.java @@ -6,6 +6,7 @@ */ package org.hibernate.jpa.internal.util; +import java.io.ObjectStreamException; import java.io.Serializable; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; @@ -16,13 +17,11 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.WeakHashMap; + import javax.persistence.spi.LoadState; import org.hibernate.HibernateException; -import org.hibernate.bytecode.enhance.spi.interceptor.AbstractLazyLoadInterceptor; 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.collection.spi.PersistentCollection; import org.hibernate.engine.spi.PersistentAttributeInterceptable; @@ -30,6 +29,9 @@ import org.hibernate.proxy.HibernateProxy; import org.hibernate.proxy.LazyInitializer; +import static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptable; +import static org.hibernate.engine.internal.ManagedTypeHelper.isPersistentAttributeInterceptable; + /** * Central delegate for handling calls from:

      *
    • {@link javax.persistence.PersistenceUtil#isLoaded(Object)}
    • @@ -82,8 +84,8 @@ public static LoadState isLoaded(Object reference) { final boolean isInitialized = !( (HibernateProxy) reference ).getHibernateLazyInitializer().isUninitialized(); return isInitialized ? LoadState.LOADED : LoadState.NOT_LOADED; } - else if ( reference instanceof PersistentAttributeInterceptable ) { - boolean isInitialized = isInitialized( (PersistentAttributeInterceptable) reference ); + else if ( isPersistentAttributeInterceptable( reference ) ) { + boolean isInitialized = isInitialized( asPersistentAttributeInterceptable( reference ) ); return isInitialized ? LoadState.LOADED : LoadState.NOT_LOADED; } else if ( reference instanceof PersistentCollection ) { @@ -130,9 +132,10 @@ public static LoadState isLoadedWithoutReference(Object entity, String attribute sureFromUs = true; } + // we are instrumenting but we can't assume we are the only ones - if ( entity instanceof PersistentAttributeInterceptable ) { - final BytecodeLazyAttributeInterceptor interceptor = extractInterceptor( (PersistentAttributeInterceptable) entity ); + if ( isPersistentAttributeInterceptable( entity ) ) { + final BytecodeLazyAttributeInterceptor interceptor = extractInterceptor( asPersistentAttributeInterceptable( entity ) ); final boolean isInitialized = interceptor == null || interceptor.isAttributeLoaded( attributeName ); LoadState state; if (isInitialized && interceptor != null) { @@ -409,24 +412,42 @@ private static Method getMethod(Class clazz, String attributeName) { } /** - * Cache hierarchy and member resolution in a weak hash map + * Cache hierarchy and member resolution, taking care to not leak + * references to Class instances. */ - //TODO not really thread-safe - public static class MetadataCache implements Serializable { - private transient Map, ClassMetadataCache> classCache = new WeakHashMap, ClassMetadataCache>(); + public static final class MetadataCache implements Serializable { + private final ClassValue metadataCacheClassValue; - private void readObject(java.io.ObjectInputStream stream) { - classCache = new WeakHashMap, ClassMetadataCache>(); + public MetadataCache() { + this( new MetadataClassValue() ); } - ClassMetadataCache getClassMetadata(Class clazz) { - ClassMetadataCache classMetadataCache = classCache.get( clazz ); - if ( classMetadataCache == null ) { - classMetadataCache = new ClassMetadataCache( clazz ); - classCache.put( clazz, classMetadataCache ); - } - return classMetadataCache; + //To help with serialization: no need to serialize the actual metadataCacheClassValue field + private MetadataCache(ClassValue metadataCacheClassValue) { + this.metadataCacheClassValue = metadataCacheClassValue; + } + + Object writeReplace() throws ObjectStreamException { + //Writing a different instance which doesn't include the cache + return new MetadataCache(null); + } + + private Object readResolve() throws ObjectStreamException { + //Ensure we do instantiate a new cache instance on deserialization + return new MetadataCache(); + } + + ClassMetadataCache getClassMetadata(final Class clazz) { + return metadataCacheClassValue.get( clazz ); + } + + } + + private static final class MetadataClassValue extends ClassValue { + @Override + protected ClassMetadataCache computeValue(final Class type) { + return new ClassMetadataCache( type ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/spi/CriteriaQueryTupleTransformer.java b/hibernate-core/src/main/java/org/hibernate/jpa/spi/CriteriaQueryTupleTransformer.java index 41c3f1d9de5b..1bca4d59610a 100644 --- a/hibernate-core/src/main/java/org/hibernate/jpa/spi/CriteriaQueryTupleTransformer.java +++ b/hibernate-core/src/main/java/org/hibernate/jpa/spi/CriteriaQueryTupleTransformer.java @@ -10,6 +10,7 @@ import javax.persistence.Tuple; import javax.persistence.TupleElement; +import org.hibernate.internal.util.type.PrimitiveWrapperHelper; import org.hibernate.query.criteria.internal.ValueHandlerFactory; import org.hibernate.transform.BasicTransformerAdapter; @@ -101,7 +102,7 @@ public Object get(String alias) { public X get(String alias, Class type) { final Object untyped = get( alias ); if ( untyped != null ) { - if ( !type.isInstance( untyped ) ) { + if (!elementTypeMatches(type, untyped)) { throw new IllegalArgumentException( String.format( "Requested tuple value [alias=%s, value=%s] cannot be assigned to requested type [%s]", @@ -126,19 +127,27 @@ public Object get(int i) { public X get(int i, Class type) { final Object result = get( i ); - if ( result != null && !type.isInstance( result ) ) { - throw new IllegalArgumentException( - String.format( - "Requested tuple value [index=%s, realType=%s] cannot be assigned to requested type [%s]", - i, - result.getClass().getName(), - type.getName() - ) - ); + if (result != null) { + if (!elementTypeMatches(type, result)) { + throw new IllegalArgumentException( + String.format( + "Requested tuple value [index=%s, realType=%s] cannot be assigned to requested type [%s]", + i, + result.getClass().getName(), + type.getName() + ) + ); + } } return (X) result; } + private boolean elementTypeMatches(Class type, Object untyped) { + return type.isInstance(untyped) + || type.isPrimitive() + && PrimitiveWrapperHelper.getDescriptorByPrimitiveType(type).getWrapperClass().isInstance(untyped); + } + public Object[] toArray() { // todo : make a copy? return tuples; diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ColumnEntityAliases.java b/hibernate-core/src/main/java/org/hibernate/loader/ColumnEntityAliases.java index 136748777995..65fbec96e096 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ColumnEntityAliases.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ColumnEntityAliases.java @@ -5,6 +5,7 @@ * See the lgpl.txt file in the root directory or . */ package org.hibernate.loader; +import java.util.Arrays; import java.util.Map; import org.hibernate.persister.entity.Loadable; @@ -35,6 +36,10 @@ protected String getDiscriminatorAlias(Loadable persister, String suffix) { } protected String[] getPropertyAliases(Loadable persister, int j) { - return persister.getPropertyColumnNames(j); + String[] propertyColumnNames = persister.getPropertyColumnNames(j); + if ( propertyColumnNames.length == 1 && propertyColumnNames[0] == null ) { + return new String[]{ persister.getPropertyNames()[j] }; + } + return propertyColumnNames; } } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/JoinWalker.java b/hibernate-core/src/main/java/org/hibernate/loader/JoinWalker.java index 2ab2859b4f05..d26e50f69c05 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/JoinWalker.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/JoinWalker.java @@ -1002,6 +1002,9 @@ protected StringBuilder whereString(String alias, String[] columnNames, int batc } } + protected StringBuilder whereString(String alias, String[] columnNames, boolean[] valueNullnes, int batchSize) { + return whereString( alias, columnNames, batchSize ); + } protected void initPersisters(final List associations, final LockMode lockMode) throws MappingException { initPersisters( associations, new LockOptions( lockMode ) ); 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 85f572a7d1ed..56b0b0f6da69 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/Loader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/Loader.java @@ -255,7 +255,7 @@ protected String preprocessSQL( SessionFactoryImplementor sessionFactory, List afterLoadActions) throws HibernateException { - Dialect dialect = sessionFactory.getServiceRegistry().getService( JdbcServices.class ).getDialect(); + final Dialect dialect = sessionFactory.getFastSessionServices().dialect; sql = applyLocks( sql, parameters, dialect, afterLoadActions ); 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 0d88f8b8a59e..ec58b41d9e9c 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; @@ -531,14 +529,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; } /** @@ -601,8 +662,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/entity/CacheEntityLoaderHelper.java b/hibernate-core/src/main/java/org/hibernate/loader/entity/CacheEntityLoaderHelper.java index 21c5c7a335d4..81fd14212a91 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/entity/CacheEntityLoaderHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/entity/CacheEntityLoaderHelper.java @@ -16,6 +16,7 @@ import org.hibernate.cache.spi.entry.ReferenceCacheEntryImpl; import org.hibernate.cache.spi.entry.StandardCacheEntryImpl; import org.hibernate.engine.internal.CacheHelper; +import org.hibernate.engine.internal.ManagedTypeHelper; import org.hibernate.engine.internal.StatefulPersistenceContext; import org.hibernate.engine.internal.TwoPhaseLoad; import org.hibernate.engine.internal.Versioning; @@ -242,7 +243,7 @@ private void makeEntityCircularReferenceSafe( // make it circular-reference safe final StatefulPersistenceContext statefulPersistenceContext = (StatefulPersistenceContext) session.getPersistenceContext(); - if ( ( entity instanceof ManagedEntity ) ) { + if ( ManagedTypeHelper.isManagedEntity( entity ) ) { statefulPersistenceContext.addReferenceEntry( entity, Status.READ_ONLY diff --git a/hibernate-core/src/main/java/org/hibernate/loader/entity/EntityJoinWalker.java b/hibernate-core/src/main/java/org/hibernate/loader/entity/EntityJoinWalker.java index 106b6d750c81..bc1d835d2183 100755 --- a/hibernate-core/src/main/java/org/hibernate/loader/entity/EntityJoinWalker.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/entity/EntityJoinWalker.java @@ -78,6 +78,26 @@ public EntityJoinWalker( this.compositeKeyManyToOneTargetIndices = callback.resolve(); } + public EntityJoinWalker( + OuterJoinLoadable persister, + String[] uniqueKey, + int batchSize, + LockOptions lockOptions, + boolean[] valueNullnes, + SessionFactoryImplementor factory, + LoadQueryInfluencers loadQueryInfluencers) throws MappingException { + super( persister, factory, loadQueryInfluencers ); + LockOptions.copy(lockOptions, this.lockOptions); + + StringBuilder whereCondition = whereString( getAlias(), uniqueKey, valueNullnes, batchSize ) + //include the discriminator and class-level where, but not filters + .append( persister.filterFragment( getAlias(), Collections.EMPTY_MAP ) ); + + AssociationInitCallbackImpl callback = new AssociationInitCallbackImpl( factory ); + initAll( whereCondition.toString(), "", lockOptions, callback ); + this.compositeKeyManyToOneTargetIndices = callback.resolve(); + } + protected JoinType getJoinType( OuterJoinLoadable persister, PropertyPath path, diff --git a/hibernate-core/src/main/java/org/hibernate/loader/entity/NaturalIdEntityJoinWalker.java b/hibernate-core/src/main/java/org/hibernate/loader/entity/NaturalIdEntityJoinWalker.java index 46ea96e9e5ec..6cb93d2f2ea7 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/entity/NaturalIdEntityJoinWalker.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/entity/NaturalIdEntityJoinWalker.java @@ -46,11 +46,34 @@ public NaturalIdEntityJoinWalker( LockOptions lockOptions, SessionFactoryImplementor factory, LoadQueryInfluencers loadQueryInfluencers) throws MappingException { - super(persister, naturalIdColumns( persister, valueNullness ), batchSize, lockOptions, factory, loadQueryInfluencers); - StringBuilder sql = new StringBuilder( getSQLString() ); + super( + persister, + naturalIdColumns( persister, valueNullness ), + batchSize, + lockOptions, + valueNullness, + factory, + loadQueryInfluencers + ); + } + + @Override + protected StringBuilder whereString(String alias, String[] columnNames, boolean[] valueNullness, int batchSize) { + StringBuilder builder = super.whereString( alias, columnNames, batchSize ); + String sql = builder.toString(); + appendNullValues( valueNullness, builder, sql.isEmpty() ); + return builder; + } + + private void appendNullValues(boolean[] valueNullness, StringBuilder whereString, boolean isFirst) { for ( String nullCol : naturalIdColumns( getPersister(), negate( valueNullness ) ) ) { - sql.append(" and ").append( getAlias() ).append('.').append( nullCol ).append(" is null"); + if ( isFirst ) { + whereString.append( getAlias() ).append( '.' ).append( nullCol ).append( " is null" ); + isFirst = false; + } + else { + whereString.append( " and " ).append( getAlias() ).append( '.' ).append( nullCol ).append( " is null" ); + } } - setSql( sql.toString() ); } } 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 3f80975a2eb8..d8054d673fd1 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 @@ -50,7 +50,7 @@ public abstract class AbstractRowReader implements RowReader { // cache map for looking up EntityReferenceInitializer by EntityReference to help with resolving // bidirectional EntityReference and fetches. - private Map entityInitializerByEntityReference; + private volatile Map entityInitializerByEntityReference; public AbstractRowReader(ReaderCollector readerCollector) { this.entityReferenceInitializers = readerCollector.getEntityReferenceInitializers().toArray( EMPTY_REFERENCE_INITIALIZERS ); @@ -149,13 +149,14 @@ else if ( CompositeFetch.class.isInstance( fetch ) ) { private EntityReferenceInitializer getInitializerByEntityReference(EntityReference targetEntityReference) { if ( entityInitializerByEntityReference == null ) { - entityInitializerByEntityReference = new HashMap<>( entityReferenceInitializers.length ); + Map entityInitializerByEntityReference = new HashMap<>( entityReferenceInitializers.length ); for ( EntityReferenceInitializer entityReferenceInitializer : entityReferenceInitializers ) { entityInitializerByEntityReference.put( entityReferenceInitializer.getEntityReference(), entityReferenceInitializer ); } + this.entityInitializerByEntityReference = entityInitializerByEntityReference; } return entityInitializerByEntityReference.get( targetEntityReference diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/ManyToOne.java b/hibernate-core/src/main/java/org/hibernate/mapping/ManyToOne.java index 22ebebf3e21b..a0d25460b5bd 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/ManyToOne.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/ManyToOne.java @@ -11,6 +11,7 @@ import java.util.Map; import org.hibernate.MappingException; +import org.hibernate.annotations.NotFoundAction; import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.type.EntityType; @@ -21,7 +22,7 @@ * @author Gavin King */ public class ManyToOne extends ToOne { - private boolean ignoreNotFound; + private NotFoundAction notFoundAction; private boolean isLogicalOneToOne; /** @@ -44,7 +45,7 @@ public Type getType() throws MappingException { getPropertyName(), isLazy(), isUnwrapProxy(), - isIgnoreNotFound(), + getNotFoundAction(), isLogicalOneToOne ); } @@ -97,12 +98,25 @@ public Object accept(ValueVisitor visitor) { return visitor.accept(this); } + public NotFoundAction getNotFoundAction() { + return notFoundAction; + } + + public void setNotFoundAction(NotFoundAction notFoundAction) { + this.notFoundAction = notFoundAction; + } + public boolean isIgnoreNotFound() { - return ignoreNotFound; + return notFoundAction == NotFoundAction.IGNORE; } public void setIgnoreNotFound(boolean ignoreNotFound) { - this.ignoreNotFound = ignoreNotFound; + if ( ignoreNotFound ) { + notFoundAction = NotFoundAction.IGNORE; + } + else { + notFoundAction = null; + } } public void markAsLogicalOneToOne() { diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/OneToMany.java b/hibernate-core/src/main/java/org/hibernate/mapping/OneToMany.java index 3f0d8204742e..01767dbf9722 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/OneToMany.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/OneToMany.java @@ -11,6 +11,7 @@ import org.hibernate.FetchMode; import org.hibernate.MappingException; +import org.hibernate.annotations.NotFoundAction; import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.engine.spi.Mapping; @@ -29,7 +30,7 @@ public class OneToMany implements Value { private String referencedEntityName; private PersistentClass associatedClass; - private boolean ignoreNotFound; + private NotFoundAction notFoundAction; /** * @deprecated Use {@link OneToMany#OneToMany(MetadataBuildingContext, PersistentClass)} instead. @@ -57,7 +58,7 @@ private EntityType getEntityType() { null, false, false, - isIgnoreNotFound(), + notFoundAction, false ); } @@ -162,12 +163,25 @@ public boolean[] getColumnUpdateability() { throw new UnsupportedOperationException(); } + public NotFoundAction getNotFoundAction() { + return notFoundAction; + } + + public void setNotFoundAction(NotFoundAction notFoundAction) { + this.notFoundAction = notFoundAction; + } + public boolean isIgnoreNotFound() { - return ignoreNotFound; + return notFoundAction == NotFoundAction.IGNORE; } public void setIgnoreNotFound(boolean ignoreNotFound) { - this.ignoreNotFound = ignoreNotFound; + if ( ignoreNotFound ) { + notFoundAction = NotFoundAction.IGNORE; + } + else { + notFoundAction = null; + } } } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/RelationalModel.java b/hibernate-core/src/main/java/org/hibernate/mapping/RelationalModel.java index 3f011773febd..79b68f4ec1e6 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/RelationalModel.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/RelationalModel.java @@ -7,6 +7,8 @@ package org.hibernate.mapping; import org.hibernate.HibernateException; import org.hibernate.boot.model.relational.SqlStringGenerationContext; +import org.hibernate.boot.model.relational.internal.SqlStringGenerationContextImpl; +import org.hibernate.dialect.Dialect; import org.hibernate.engine.spi.Mapping; /** @@ -17,6 +19,19 @@ */ @Deprecated public interface RelationalModel { + @Deprecated + default String sqlCreateString(Dialect dialect, Mapping p, String defaultCatalog, String defaultSchema) throws HibernateException { + return sqlCreateString( p, SqlStringGenerationContextImpl.forBackwardsCompatibility( dialect, defaultCatalog, defaultSchema ), + defaultCatalog, defaultSchema ); + } + String sqlCreateString(Mapping p, SqlStringGenerationContext context, String defaultCatalog, String defaultSchema) throws HibernateException; + + @Deprecated + default String sqlDropString(Dialect dialect, String defaultCatalog, String defaultSchema) throws HibernateException { + return sqlDropString( SqlStringGenerationContextImpl.forBackwardsCompatibility( dialect, defaultCatalog, defaultSchema ), + defaultCatalog, defaultSchema ); + } + String sqlDropString(SqlStringGenerationContext context, String defaultCatalog, String defaultSchema); } 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 19c63b85033a..9214bcace126 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 @@ -91,7 +91,8 @@ public class MetamodelImpl implements MetamodelImplementor, Serializable { private final SessionFactoryImplementor sessionFactory; - private final Map imports = new ConcurrentHashMap<>(); + private final Map knownValidImports = new ConcurrentHashMap<>(); + private final Map knownInvalidImports = new ConcurrentHashMap<>(); private final Map entityPersisterMap = new ConcurrentHashMap<>(); private final Map entityProxyInterfaceMap = new ConcurrentHashMap<>(); private final Map collectionPersisterMap = new ConcurrentHashMap<>(); @@ -155,7 +156,7 @@ public MetamodelImpl(SessionFactoryImplementor sessionFactory, TypeConfiguration * @param jpaMetaModelPopulationSetting Should the JPA Metamodel be built as well? */ public void initialize(MetadataImplementor mappingMetadata, JpaMetaModelPopulationSetting jpaMetaModelPopulationSetting) { - this.imports.putAll( mappingMetadata.getImports() ); + this.knownValidImports.putAll( mappingMetadata.getImports() ); primeSecondLevelCacheRegions( mappingMetadata ); @@ -623,33 +624,37 @@ public EntityTypeDescriptor entity(String entityName) { } @Override - public String getImportedClassName(String className) { - String result = imports.get( className ); - if ( result == null ) { - try { - sessionFactory.getServiceRegistry().getService( ClassLoaderService.class ).classForName( className ); - imports.put( className, className ); - return className; - } - catch ( ClassLoadingException cnfe ) { - // This check doesn't necessarily mean that the map can't exceed 1000 elements because - // new entries might be added _while_ performing the check (making it 1000+ since size() isn't - // synchronized). Regardless, this would pass as "good enough" to prevent the map from growing - // above a certain threshold, thus, avoiding memory issues. - if ( imports.size() < 1_000 ) { - imports.put( className, INVALID_IMPORT ); - } - return null; - } + public String getImportedClassName(final String className) { + final String result = knownValidImports.get( className ); + if ( result != null ) { + //optimal path: + return result; } else { - // explicitly check for same instance - //noinspection StringEquality - if ( result == INVALID_IMPORT ) { + //check the negative cache first, to avoid ClassLoadingException for commonly used strings which aren't class names: + if ( knownInvalidImports.containsKey( className ) ) { return null; } else { - return result; + //either we've not seen this string yet, or the negative cache has grown too much; + //either way we need to attempt a regular class load: + try { + sessionFactory.getServiceRegistry().getService( ClassLoaderService.class ).classForName( className ); + //Store this information in the cache: + knownValidImports.put( className, className ); + return className; + } + catch ( ClassLoadingException cnfe ) { + // This check doesn't necessarily mean that the map can't exceed 1000 elements because + // new entries might be added _while_ performing the check (making it 1000+ since size() isn't + // synchronized). Regardless, this would pass as "good enough" to prevent the map from growing + // above a certain threshold, thus, avoiding memory issues. + if ( knownInvalidImports.size() < 1_000 ) { + //Store this in the negative cache, but only if it's not getting too large. + knownInvalidImports.put( className, INVALID_IMPORT ); + } + return null; + } } } } 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 366450c3048b..ac7e414ac83b 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 @@ -2287,7 +2287,7 @@ public int[] resolveDirtyAttributeIndexes( // We have to check the state for "mutable" properties as dirty tracking isn't aware of mutable types final Type[] propertyTypes = entityMetamodel.getPropertyTypes(); final boolean[] propertyCheckability = entityMetamodel.getPropertyCheckability(); - mutablePropertiesIndexes.stream().forEach( i -> { + for ( int i = mutablePropertiesIndexes.nextSetBit(0); i >= 0; i = mutablePropertiesIndexes.nextSetBit(i + 1) ) { // This is kindly borrowed from org.hibernate.type.TypeHelper.findDirty final boolean dirty = currentState[i] != LazyPropertyInitializer.UNFETCHED_PROPERTY && // Consider mutable properties as dirty if we don't have a previous state @@ -2302,7 +2302,7 @@ public int[] resolveDirtyAttributeIndexes( if ( dirty ) { fields.add( i ); } - } ); + } } if ( attributeNames != null ) { @@ -2941,7 +2941,7 @@ else if ( includeProperty[i] ) { // append the SQL to return the generated identifier if ( j == 0 && identityInsert && useInsertSelectIdentity() ) { //TODO: suck into Insert - result = getFactory().getDialect().getIdentityColumnSupport().appendIdentitySelectToInsert( result ); + result = getFactory().getDialect().getIdentityColumnSupport().appendIdentitySelectToInsert( getKeyColumns( 0 )[0], result ); } return result; @@ -3382,7 +3382,7 @@ public void insert( ); } } - catch (SQLException | JDBCException e) { + catch (SQLException | RuntimeException e) { if ( useBatch ) { session.getJdbcCoordinator().abortBatch(); } @@ -3583,7 +3583,7 @@ else if ( isAllOrDirtyOptLocking() && oldFields != null ) { } } - catch (SQLException e) { + catch (SQLException | RuntimeException e) { if ( useBatch ) { session.getJdbcCoordinator().abortBatch(); } @@ -3709,11 +3709,11 @@ else if ( isAllOrDirtyOptLocking() && loadedState != null ) { } } - catch (SQLException sqle) { + catch (SQLException | RuntimeException e) { if ( useBatch ) { session.getJdbcCoordinator().abortBatch(); } - throw sqle; + throw e; } finally { if ( !useBatch ) { @@ -3806,7 +3806,7 @@ public void update( if ( entry == null && !isMutable() ) { throw new IllegalStateException( "Updating immutable entity that is not in session yet!" ); } - if ( ( entityMetamodel.isDynamicUpdate() && dirtyFields != null ) ) { + if ( dirtyFields != null && entityMetamodel.isDynamicUpdate() ) { // We need to generate the UPDATE SQL when dynamic-update="true" propsToUpdate = getPropertiesToUpdate( dirtyFields, hasDirtyCollection ); // don't need to check laziness (dirty checking algorithm handles that) @@ -3817,6 +3817,24 @@ public void update( null; } } + else if ( dirtyFields != null && hasUninitializedLazyProperties( object ) && hasLazyDirtyFields( dirtyFields ) ) { + // We need to generate the UPDATE SQL when there are dirty lazy fields + propsToUpdate = getPropertiesToUpdate( dirtyFields, hasDirtyCollection ); + // don't need to check laziness (dirty checking algorithm handles that) + final boolean[] propertyLaziness = getPropertyLaziness(); + // we add also all the non lazy properties because dynamic update is false + for ( int i = 0; i < propertyLaziness.length; i++ ) { + if ( propertyLaziness[i] == false ) { + propsToUpdate[i] = true; + } + } + updateStrings = new String[span]; + for ( int j = 0; j < span; j++ ) { + updateStrings[j] = tableUpdateNeeded[j] ? + generateUpdateString( propsToUpdate, j, oldFields, j == 0 && rowId != null ) : + null; + } + } else if ( !isModifiableEntity( entry ) ) { // We need to generate UPDATE SQL when a non-modifiable entity (e.g., read-only or immutable) // needs: @@ -3865,6 +3883,17 @@ else if ( !isModifiableEntity( entry ) ) { } } + private boolean hasLazyDirtyFields(int[] dirtyFields) { + final boolean[] propertyLaziness = getPropertyLaziness(); + for ( int i = 0; i < dirtyFields.length; i++ ) { + if ( propertyLaziness[dirtyFields[i]] ) { + return true; + } + } + return false; + } + + @Override public Serializable insert(Object[] fields, Object object, SharedSessionContractImplementor session) throws HibernateException { // apply any pre-insert in-memory value generation 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 fd37e9955bdb..e5c7e6cb3fb7 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 @@ -26,6 +26,7 @@ import org.hibernate.type.AssociationType; import org.hibernate.type.CollectionType; import org.hibernate.type.CompositeType; +import org.hibernate.type.EmbeddedComponentType; import org.hibernate.type.EntityType; import org.hibernate.type.ManyToOneType; import org.hibernate.type.OneToOneType; @@ -405,10 +406,15 @@ protected void initIdentifierPropertyPaths( } } - if ( (! etype.isNullable() ) && idPropName != null ) { - String idpath2 = extendPath( path, idPropName ); - addPropertyPath( idpath2, idtype, columns, columnReaders, columnReaderTemplates, formulaTemplates, factory ); - initPropertyPaths( idpath2, idtype, columns, columnReaders, columnReaderTemplates, formulaTemplates, factory ); + if ( ( !etype.isNullable() ) ) { + if ( idPropName != null ) { + String idpath2 = extendPath( path, idPropName ); + addPropertyPath( idpath2, idtype, columns, columnReaders, columnReaderTemplates, formulaTemplates, factory ); + initPropertyPaths( idpath2, idtype, columns, columnReaders, columnReaderTemplates, formulaTemplates, factory ); + } + else if ( idtype.isComponentType() && idtype instanceof EmbeddedComponentType ) { + initComponentPropertyPaths( path, (CompositeType) idtype, columns, columnReaders, columnReaderTemplates, formulaTemplates, factory ); + } } } 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 acfa97e9e77b..84591e437727 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 @@ -35,6 +35,7 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.StringHelper; +import org.hibernate.jpa.TypedParameterValue; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.procedure.NoSuchParameterException; import org.hibernate.procedure.ParameterRegistration; @@ -66,6 +67,7 @@ * Standard implementation of {@link org.hibernate.procedure.ProcedureCall} * * @author Steve Ebersole + * @author Yanming Zhou */ public class ProcedureCallImpl extends AbstractProducedQuery @@ -814,13 +816,25 @@ public

      ProcedureCallImplementor setParameter(Parameter

      parameter, P va @Override public ProcedureCallImplementor setParameter(String name, Object value) { - paramBindings.getBinding( getParameterMetadata().getQueryParameter( name ) ).setBindValue( value ); + QueryParameterBinding binding = paramBindings.getBinding( getParameterMetadata().getQueryParameter( name ) ); + if ( value instanceof TypedParameterValue ) { + binding.setBindValue( ( (TypedParameterValue) value ).getValue(), ( (TypedParameterValue) value ).getType() ); + } + else { + binding.setBindValue( value ); + } return this; } @Override public ProcedureCallImplementor setParameter(int position, Object value) { - paramBindings.getBinding( getParameterMetadata().getQueryParameter( position ) ).setBindValue( value ); + QueryParameterBinding binding = paramBindings.getBinding( getParameterMetadata().getQueryParameter( position ) ); + if ( value instanceof TypedParameterValue ) { + binding.setBindValue( ( (TypedParameterValue) value ).getValue(), ( (TypedParameterValue) value ).getType() ); + } + else { + binding.setBindValue( value ); + } return this; } diff --git a/hibernate-core/src/main/java/org/hibernate/property/access/internal/PropertyAccessStrategyMapImpl.java b/hibernate-core/src/main/java/org/hibernate/property/access/internal/PropertyAccessStrategyMapImpl.java index 539e26a0fc55..92b6fcc19ba2 100644 --- a/hibernate-core/src/main/java/org/hibernate/property/access/internal/PropertyAccessStrategyMapImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/property/access/internal/PropertyAccessStrategyMapImpl.java @@ -6,7 +6,8 @@ */ package org.hibernate.property.access.internal; -import org.hibernate.mapping.Map; +import java.util.Map; + import org.hibernate.property.access.spi.PropertyAccess; import org.hibernate.property.access.spi.PropertyAccessStrategy; @@ -24,7 +25,7 @@ public class PropertyAccessStrategyMapImpl implements PropertyAccessStrategy { public PropertyAccess buildPropertyAccess(Class containerJavaType, String propertyName) { // Sometimes containerJavaType is null, but if it isn't, make sure it's a Map. - if (containerJavaType != null && !Map.class.isAssignableFrom(containerJavaType)) { + if (containerJavaType != null && !Map.class.isAssignableFrom( containerJavaType)) { throw new IllegalArgumentException( String.format( "Expecting class: [%1$s], but containerJavaType is of type: [%2$s] for propertyName: [%3$s]", diff --git a/hibernate-core/src/main/java/org/hibernate/property/access/internal/PropertyAccessStrategyResolverStandardImpl.java b/hibernate-core/src/main/java/org/hibernate/property/access/internal/PropertyAccessStrategyResolverStandardImpl.java index 836da1003336..2263c333d9cb 100644 --- a/hibernate-core/src/main/java/org/hibernate/property/access/internal/PropertyAccessStrategyResolverStandardImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/property/access/internal/PropertyAccessStrategyResolverStandardImpl.java @@ -9,7 +9,7 @@ import org.hibernate.EntityMode; import org.hibernate.HibernateException; import org.hibernate.boot.registry.selector.spi.StrategySelector; -import org.hibernate.engine.spi.Managed; +import org.hibernate.engine.internal.ManagedTypeHelper; import org.hibernate.internal.util.StringHelper; import org.hibernate.property.access.spi.BuiltInPropertyAccessStrategies; import org.hibernate.property.access.spi.PropertyAccessStrategy; @@ -37,7 +37,8 @@ public PropertyAccessStrategy resolvePropertyAccessStrategy( if ( BuiltInPropertyAccessStrategies.BASIC.getExternalName().equals( explicitAccessStrategyName ) || BuiltInPropertyAccessStrategies.FIELD.getExternalName().equals( explicitAccessStrategyName ) || BuiltInPropertyAccessStrategies.MIXED.getExternalName().equals( explicitAccessStrategyName ) ) { - if ( Managed.class.isAssignableFrom( containerClass ) ) { + //type-cache-pollution agent: always check for EnhancedEntity type first. + if ( ManagedTypeHelper.isManagedType( containerClass ) ) { // PROPERTY (BASIC) and MIXED are not valid for bytecode enhanced entities... return PropertyAccessStrategyEnhancedImpl.INSTANCE; } 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 27dbb9efe3f0..a89df630cedb 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 @@ -10,6 +10,7 @@ import java.lang.reflect.Field; import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor; +import org.hibernate.engine.internal.ManagedTypeHelper; import org.hibernate.engine.spi.CompositeOwner; import org.hibernate.engine.spi.CompositeTracker; import org.hibernate.engine.spi.PersistentAttributeInterceptable; @@ -46,11 +47,13 @@ public void set(Object target, Object value, SessionFactoryImplementor factory) } // This marks the attribute as initialized, so it doesn't get lazily loaded afterwards - if ( target instanceof PersistentAttributeInterceptable ) { - PersistentAttributeInterceptor interceptor = ( (PersistentAttributeInterceptable) target ).$$_hibernate_getInterceptor(); - if ( interceptor instanceof BytecodeLazyAttributeInterceptor ) { - ( (BytecodeLazyAttributeInterceptor) interceptor ).attributeInitialized( propertyName ); - } + ManagedTypeHelper.processIfPersistentAttributeInterceptable( target, EnhancedSetterImpl::setAttributeInitialized, propertyName ); + } + + private static void setAttributeInitialized(PersistentAttributeInterceptable target, String propertyName) { + PersistentAttributeInterceptor interceptor = target.$$_hibernate_getInterceptor(); + if ( interceptor instanceof BytecodeLazyAttributeInterceptor ) { + ( (BytecodeLazyAttributeInterceptor) interceptor ).attributeInitialized( propertyName ); } } 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 index dc6687146043..40abc800be43 100644 --- 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 @@ -6,10 +6,12 @@ */ 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.Collection; import java.util.HashSet; import java.util.Locale; import java.util.Set; @@ -27,11 +29,13 @@ import net.bytebuddy.NamingStrategy; import net.bytebuddy.TypeCache; import net.bytebuddy.description.modifier.Visibility; +import net.bytebuddy.description.type.TypeDefinition; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.description.type.TypeList; import net.bytebuddy.dynamic.DynamicType; import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy; import net.bytebuddy.implementation.SuperMethodCall; - -import static org.hibernate.internal.CoreLogging.messageLogger; +import net.bytebuddy.pool.TypePool; public class ByteBuddyProxyHelper implements Serializable { @@ -52,30 +56,42 @@ public Class buildProxy(final Class persistentClass, final Class[] interfaces) { } key.addAll( Arrays.>asList( interfaces ) ); - return byteBuddyState.loadProxy( persistentClass, new TypeCache.SimpleKey( key ), proxyBuilder( persistentClass, interfaces ) ); + return byteBuddyState.loadProxy( persistentClass, new TypeCache.SimpleKey( key ), + proxyBuilder( TypeDescription.ForLoadedType.of( persistentClass ), new TypeList.Generic.ForLoadedTypes( interfaces ) ) ); + } + + /** + * @deprecated Use {@link #buildUnloadedProxy(TypePool, TypeDefinition, Collection)} instead. + */ + @Deprecated + public DynamicType.Unloaded buildUnloadedProxy(final Class persistentClass, final Class[] interfaces) { + return byteBuddyState.make( proxyBuilder( TypeDescription.ForLoadedType.of( persistentClass ), + new TypeList.Generic.ForLoadedTypes( interfaces ) ) ); } /** * Do not remove: used by Quarkus */ - @SuppressWarnings({ "unchecked", "rawtypes" }) - public DynamicType.Unloaded buildUnloadedProxy(final Class persistentClass, final Class[] interfaces) { - return byteBuddyState.make( proxyBuilder( persistentClass, interfaces ) ); + public DynamicType.Unloaded buildUnloadedProxy(TypePool typePool, TypeDefinition persistentClass, + Collection interfaces) { + return byteBuddyState.make( typePool, proxyBuilder( persistentClass, interfaces ) ); } - private Function> proxyBuilder(Class persistentClass, Class[] interfaces) { + private Function> proxyBuilder(TypeDefinition persistentClass, + Collection interfaces) { + ByteBuddyState.ProxyDefinitionHelpers helpers = byteBuddyState.getProxyDefinitionHelpers(); return 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() ) + .ignore( helpers.getGroovyGetMetaClassFilter() ) + .with( new NamingStrategy.SuffixingRandom( PROXY_NAMING_SUFFIX, new NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue( persistentClass.getTypeName() ) ) ) + .subclass( interfaces.size() == 1 ? persistentClass : TypeDescription.OBJECT, ConstructorStrategy.Default.IMITATE_SUPER_CLASS_OPENING ) + .implement( interfaces ) + .method( helpers.getVirtualNotFinalizerFilter() ) + .intercept( helpers.getDelegateToInterceptorDispatcherMethodDelegation() ) + .method( helpers.getProxyNonInterceptedMethodFilter() ) .intercept( SuperMethodCall.INSTANCE ) .defineField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME, ProxyConfiguration.Interceptor.class, Visibility.PRIVATE ) .implement( ProxyConfiguration.class ) - .intercept( byteBuddyState.getProxyDefinitionHelpers().getInterceptorFieldAccessor() ); + .intercept( helpers.getInterceptorFieldAccessor() ); } public HibernateProxy deserializeProxy(SerializableProxy serializableProxy) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/CriteriaUpdateImpl.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/CriteriaUpdateImpl.java index 3e4b9a0afde9..682c9d9e05e0 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/CriteriaUpdateImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/CriteriaUpdateImpl.java @@ -8,6 +8,7 @@ import java.util.ArrayList; import java.util.List; +import javax.persistence.Parameter; import javax.persistence.criteria.CriteriaUpdate; import javax.persistence.criteria.Expression; import javax.persistence.criteria.Path; @@ -69,9 +70,15 @@ public CriteriaUpdate set(Path attributePath, Expression @SuppressWarnings("unchecked") public CriteriaUpdate set(String attributeName, Object value) { final Path attributePath = getRoot().get( attributeName ); - final Expression valueExpression = value == null - ? criteriaBuilder().nullLiteral( attributePath.getJavaType() ) - : criteriaBuilder().literal( value ); + final Expression valueExpression; + if ( value instanceof Expression ) { + valueExpression = (Expression) value; + } + else { + valueExpression = value == null + ? criteriaBuilder().nullLiteral( attributePath.getJavaType() ) + : criteriaBuilder().literal( value ); + } addAssignment( attributePath, valueExpression ); return this; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/compile/CriteriaCompiler.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/compile/CriteriaCompiler.java index e00e8ad5b222..d4c685c9d543 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/compile/CriteriaCompiler.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/compile/CriteriaCompiler.java @@ -11,6 +11,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import javax.persistence.Parameter; import javax.persistence.TypedQuery; import javax.persistence.criteria.ParameterExpression; @@ -22,6 +23,7 @@ import org.hibernate.internal.util.collections.Stack; import org.hibernate.internal.util.collections.StandardStack; import org.hibernate.query.criteria.LiteralHandlingMode; +import org.hibernate.query.criteria.internal.expression.ParameterExpressionImpl; import org.hibernate.query.criteria.internal.expression.function.FunctionExpression; import org.hibernate.query.spi.QueryImplementor; import org.hibernate.sql.ast.Clause; @@ -92,7 +94,7 @@ public Stack getFunctionStack() { public ExplicitParameterInfo registerExplicitParameter(ParameterExpression criteriaQueryParameter) { ExplicitParameterInfo parameterInfo = explicitParameterInfoMap.get( criteriaQueryParameter ); if ( parameterInfo == null ) { - if ( StringHelper.isNotEmpty( criteriaQueryParameter.getName() ) ) { + if ( StringHelper.isNotEmpty( criteriaQueryParameter.getName() ) && !( (ParameterExpressionImpl) criteriaQueryParameter ).isNameGenerated() ) { parameterInfo = new ExplicitParameterInfo( criteriaQueryParameter.getName(), null, @@ -132,6 +134,9 @@ public Class getJavaType() { } public void bind(TypedQuery typedQuery) { + if ( literal instanceof Parameter ) { + return; + } typedQuery.setParameter( parameterName, literal ); } }; 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 3bac2a93ccf1..4c565350cf07 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 @@ -23,8 +23,9 @@ public class ParameterExpressionImpl extends ExpressionImpl implements ParameterExpression, Serializable { - private final String name; + private String name; private final Integer position; + private boolean isNameGenerated; public ParameterExpressionImpl( CriteriaBuilderImpl criteriaBuilder, @@ -57,6 +58,10 @@ public String getName() { return name; } + public boolean isNameGenerated() { + return isNameGenerated; + } + @Override public Integer getPosition() { return position; @@ -75,6 +80,10 @@ public void registerParameters(ParameterRegistry registry) { @Override public String render(RenderingContext renderingContext) { final ExplicitParameterInfo parameterInfo = renderingContext.registerExplicitParameter( this ); + if ( name == null && position == null ) { + isNameGenerated = true; + name = parameterInfo.getName(); + } return parameterInfo.render(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/resource/beans/container/internal/CdiBeanContainerBuilder.java b/hibernate-core/src/main/java/org/hibernate/resource/beans/container/internal/CdiBeanContainerBuilder.java index bf79c91cd334..11d05aa4baa1 100644 --- a/hibernate-core/src/main/java/org/hibernate/resource/beans/container/internal/CdiBeanContainerBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/resource/beans/container/internal/CdiBeanContainerBuilder.java @@ -62,12 +62,7 @@ public static BeanContainer fromBeanManagerReference( () -> cfgService.getSetting( AvailableSettings.DELAY_CDI_ACCESS, StandardConverters.BOOLEAN ), () -> { final Boolean oldSetting = cfgService.getSetting( org.hibernate.jpa.AvailableSettings.DELAY_CDI_ACCESS, StandardConverters.BOOLEAN ); - if ( oldSetting != null ) { - DeprecationLogger.DEPRECATION_LOGGER.deprecatedSetting( - org.hibernate.jpa.AvailableSettings.DELAY_CDI_ACCESS, - AvailableSettings.DELAY_CDI_ACCESS - ); - } + //Not invoking the DeprecationLogger in this case as the user can't avoid using this property (the string value is the same) return oldSetting; }, () -> false diff --git a/hibernate-core/src/main/java/org/hibernate/resource/beans/container/internal/JpaCompliantLifecycleStrategy.java b/hibernate-core/src/main/java/org/hibernate/resource/beans/container/internal/JpaCompliantLifecycleStrategy.java index 4d76417fa88f..f43765f30d76 100644 --- a/hibernate-core/src/main/java/org/hibernate/resource/beans/container/internal/JpaCompliantLifecycleStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/resource/beans/container/internal/JpaCompliantLifecycleStrategy.java @@ -113,7 +113,7 @@ public void initialize() { } try { - this.injectionTarget = beanManager.createInjectionTarget( annotatedType ); + this.injectionTarget = beanManager.getInjectionTargetFactory( annotatedType ).createInjectionTarget( (Bean) null ); this.creationalContext = beanManager.createCreationalContext( null ); this.beanInstance = this.injectionTarget.produce( creationalContext ); diff --git a/hibernate-core/src/main/java/org/hibernate/service/internal/AbstractServiceRegistryImpl.java b/hibernate-core/src/main/java/org/hibernate/service/internal/AbstractServiceRegistryImpl.java index 5599da1378d4..ebd1e91bb3ac 100644 --- a/hibernate-core/src/main/java/org/hibernate/service/internal/AbstractServiceRegistryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/service/internal/AbstractServiceRegistryImpl.java @@ -17,6 +17,7 @@ import java.util.function.Consumer; import org.hibernate.boot.registry.BootstrapServiceRegistry; +import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.cfg.Environment; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; @@ -191,6 +192,14 @@ private void registerAlternate(Class alternate, Class target) { @Override public R getService(Class serviceRole) { + //Fast-path for ClassLoaderService as it's extremely hot during bootstrap + //(and after bootstrap service loading performance is less interesting as it's + //ideally being cached by long term consumers) + if ( ClassLoaderService.class.equals( serviceRole ) ) { + if ( parent != null ) { + return parent.getService( serviceRole ); + } + } // TODO: should an exception be thrown if active == false??? R service = serviceRole.cast( initializedServiceByRole.get( serviceRole ) ); if ( service != null ) { diff --git a/hibernate-core/src/main/java/org/hibernate/sql/JoinType.java b/hibernate-core/src/main/java/org/hibernate/sql/JoinType.java index 0155e5a65bc8..2f0afe281da3 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/JoinType.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/JoinType.java @@ -13,21 +13,28 @@ */ public enum JoinType { - NONE( -666 ), - INNER_JOIN( 0 ), - LEFT_OUTER_JOIN( 1 ), - RIGHT_OUTER_JOIN( 2 ), - FULL_JOIN( 4 ); - private int joinTypeValue; - - JoinType(int joinTypeValue) { + NONE( -666, null ), + INNER_JOIN( 0, "inner" ), + LEFT_OUTER_JOIN( 1, "left" ), + RIGHT_OUTER_JOIN( 2, "right" ), + FULL_JOIN( 4, "full" ); + + private final int joinTypeValue; + private final String sqlText; + + JoinType(int joinTypeValue, String sqlText) { this.joinTypeValue = joinTypeValue; + this.sqlText = sqlText; } public int getJoinTypeValue() { return joinTypeValue; } + public String getSqlText() { + return sqlText; + } + public static JoinType parse(int joinType) { if ( joinType < 0 ) { return NONE; diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/Action.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/Action.java index 8265628b3224..cb7ed8d2a51f 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/Action.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/Action.java @@ -76,6 +76,14 @@ public enum Action { this.externalHbm2ddlName = externalHbm2ddlName; } + public String getExternalJpaName() { + return externalJpaName; + } + + public String getExternalHbm2ddlName() { + return externalHbm2ddlName; + } + @Override public String toString() { return getClass().getSimpleName() + "(externalJpaName=" + externalJpaName + ", externalHbm2ddlName=" + externalHbm2ddlName + ")"; diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/internal/AbstractInformationExtractorImpl.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/internal/AbstractInformationExtractorImpl.java index 3a6961dc9a04..d9c605ba7ec5 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/internal/AbstractInformationExtractorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/internal/AbstractInformationExtractorImpl.java @@ -25,6 +25,8 @@ import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.relational.QualifiedTableName; import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.DB2Dialect; +import org.hibernate.dialect.Dialect; import org.hibernate.engine.config.spi.ConfigurationService; import org.hibernate.engine.config.spi.StandardConverters; import org.hibernate.engine.jdbc.env.spi.IdentifierHelper; @@ -95,17 +97,21 @@ public AbstractInformationExtractorImpl(ExtractionContext extractionContext) { ) ); } - extractionContext.getJdbcEnvironment().getDialect().augmentPhysicalTableTypes( physicalTableTypesList ); + final Dialect dialect = extractionContext.getJdbcEnvironment().getDialect(); + dialect.augmentPhysicalTableTypes( physicalTableTypesList ); this.extraPhysicalTableTypes = physicalTableTypesList.toArray( new String[0] ); final List tableTypesList = new ArrayList<>(); tableTypesList.add( "TABLE" ); tableTypesList.add( "VIEW" ); if ( ConfigurationHelper.getBoolean( AvailableSettings.ENABLE_SYNONYMS, configService.getSettings(), false ) ) { + if ( dialect instanceof DB2Dialect ) { + tableTypesList.add( "ALIAS" ); + } tableTypesList.add( "SYNONYM" ); } Collections.addAll( tableTypesList, extraPhysicalTableTypes ); - extractionContext.getJdbcEnvironment().getDialect().augmentRecognizedTableTypes( tableTypesList ); + dialect.augmentRecognizedTableTypes( tableTypesList ); this.tableTypes = tableTypesList.toArray( new String[ tableTypesList.size() ] ); } diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/internal/SequenceInformationExtractorMariaDBDatabaseImpl.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/internal/SequenceInformationExtractorMariaDBDatabaseImpl.java index e58f5fb19671..5289eb8c8ff4 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/internal/SequenceInformationExtractorMariaDBDatabaseImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/internal/SequenceInformationExtractorMariaDBDatabaseImpl.java @@ -12,6 +12,7 @@ import java.util.Collections; import java.util.List; +import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.relational.QualifiedSequenceName; import org.hibernate.engine.jdbc.env.spi.IdentifierHelper; import org.hibernate.tool.schema.extract.spi.ExtractionContext; @@ -28,7 +29,7 @@ public class SequenceInformationExtractorMariaDBDatabaseImpl extends SequenceInf // SQL to get metadata from individual sequence private static final String SQL_SEQUENCE_QUERY = - "SELECT '%1$s' as sequence_name, minimum_value, maximum_value, start_value, increment, cache_size FROM %1$s "; + "SELECT '%1$s' as sequence_name, minimum_value, maximum_value, start_value, increment, cache_size FROM %2$s "; private static final String UNION_ALL = "UNION ALL "; @@ -56,7 +57,7 @@ public Iterable extractMetadata(ExtractionContext extractio if ( sequenceInfoQueryBuilder.length() > 0 ) { sequenceInfoQueryBuilder.append( UNION_ALL ); } - sequenceInfoQueryBuilder.append( String.format( SQL_SEQUENCE_QUERY, sequenceName ) ); + sequenceInfoQueryBuilder.append( String.format( SQL_SEQUENCE_QUERY, sequenceName, Identifier.toIdentifier( sequenceName, false, true ) ) ); } return extractionContext.getQueryResults( sequenceInfoQueryBuilder.toString(), diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/internal/SequenceInformationExtractorOracleDatabaseImpl.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/internal/SequenceInformationExtractorOracleDatabaseImpl.java index b4706177b904..0279fcd96b93 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/internal/SequenceInformationExtractorOracleDatabaseImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/internal/SequenceInformationExtractorOracleDatabaseImpl.java @@ -6,6 +6,7 @@ */ package org.hibernate.tool.schema.extract.internal; +import java.math.BigDecimal; import java.sql.ResultSet; import java.sql.SQLException; @@ -18,6 +19,9 @@ public class SequenceInformationExtractorOracleDatabaseImpl extends SequenceInfo */ public static final SequenceInformationExtractorOracleDatabaseImpl INSTANCE = new SequenceInformationExtractorOracleDatabaseImpl(); + private static final BigDecimal MIN_VALUE = BigDecimal.valueOf( Long.MIN_VALUE ); + private static final BigDecimal MAX_VALUE = BigDecimal.valueOf( Long.MAX_VALUE ); + @Override protected String sequenceCatalogColumn() { return null; @@ -38,9 +42,33 @@ protected String sequenceMinValueColumn() { return "min_value"; } + @Override + protected String sequenceMaxValueColumn() { + return "max_value"; + } + + @Override + protected Long resultSetMinValue(ResultSet resultSet) throws SQLException { + final BigDecimal asDecimal = resultSet.getBigDecimal( sequenceMinValueColumn() ); + + // BigDecimal.longValue() may return a result with the opposite sign + if ( asDecimal.compareTo( MIN_VALUE ) == -1 ) { + return Long.MIN_VALUE; + } + + return asDecimal.longValue(); + } + @Override protected Long resultSetMaxValue(ResultSet resultSet) throws SQLException { - return resultSet.getBigDecimal( "max_value" ).longValue(); + final BigDecimal asDecimal = resultSet.getBigDecimal( sequenceMaxValueColumn() ); + + // BigDecimal.longValue() may return a result with the opposite sign + if ( asDecimal.compareTo( MAX_VALUE ) == 1 ) { + return Long.MAX_VALUE; + } + + return asDecimal.longValue(); } @Override 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 815febc060e5..59f457dafcd3 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 @@ -535,14 +535,18 @@ protected void createSchemaAndCatalog( Formatter formatter, boolean tryToCreateCatalogs, boolean tryToCreateSchemas, - Set exportedCatalogs, Namespace namespace, GenerationTarget[] targets) { + Set exportedCatalogs, Namespace namespace, + SqlStringGenerationContext context, + GenerationTarget[] targets) { if ( tryToCreateCatalogs || tryToCreateSchemas ) { - if ( tryToCreateCatalogs ) { - final Identifier catalogLogicalName = namespace.getName().getCatalog(); - final Identifier catalogPhysicalName = namespace.getPhysicalName().getCatalog(); + Namespace.Name logicalName = namespace.getName(); + Namespace.Name physicalName = namespace.getPhysicalName(); + if ( tryToCreateCatalogs ) { + final Identifier catalogLogicalName = logicalName.getCatalog(); + final Identifier catalogPhysicalName = context.catalogWithDefault( physicalName.getCatalog() ); if ( catalogPhysicalName != null && !exportedCatalogs.contains( catalogLogicalName ) - && !existingDatabase.catalogExists( catalogLogicalName ) ) { + && !existingDatabase.catalogExists( catalogPhysicalName ) ) { applySqlStrings( false, dialect.getCreateCatalogCommand( catalogPhysicalName.render( dialect ) ), @@ -554,16 +558,17 @@ protected void createSchemaAndCatalog( } } - if ( tryToCreateSchemas - && namespace.getPhysicalName().getSchema() != null - && !existingDatabase.schemaExists( namespace.getName() ) ) { - applySqlStrings( - false, - dialect.getCreateSchemaCommand( namespace.getPhysicalName().getSchema().render( dialect ) ), - formatter, - options, - targets - ); + if ( tryToCreateSchemas ) { + final Identifier schemaPhysicalName = context.schemaWithDefault( physicalName.getSchema() ); + if ( schemaPhysicalName != null && !existingDatabase.schemaExists( physicalName ) ) { + applySqlStrings( + false, + dialect.getCreateSchemaCommand( schemaPhysicalName.render( dialect ) ), + formatter, + options, + targets + ); + } } } } diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/GroupedSchemaMigratorImpl.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/GroupedSchemaMigratorImpl.java index 47d631cb2c2d..a6604c1bca15 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/GroupedSchemaMigratorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/GroupedSchemaMigratorImpl.java @@ -63,6 +63,7 @@ protected NameSpaceTablesInformation performTablesMigration( tryToCreateSchemas, exportedCatalogs, namespace, + sqlStringGenerationContext, targets ); final NameSpaceTablesInformation tables = existingDatabase.getTablesInformation( namespace ); diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/IndividuallySchemaMigratorImpl.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/IndividuallySchemaMigratorImpl.java index de510ae1c60a..bd7d59954357 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/IndividuallySchemaMigratorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/IndividuallySchemaMigratorImpl.java @@ -63,6 +63,7 @@ protected NameSpaceTablesInformation performTablesMigration( tryToCreateSchemas, exportedCatalogs, namespace, + sqlStringGenerationContext, targets ); for ( Table table : namespace.getTables() ) { diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/SchemaCreatorImpl.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/SchemaCreatorImpl.java index 529558e55c09..01c6260afdf2 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/SchemaCreatorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/SchemaCreatorImpl.java @@ -233,11 +233,12 @@ public void createFromMetadata( continue; } - if ( tryToCreateCatalogs ) { - final Identifier catalogLogicalName = namespace.getName().getCatalog(); - final Identifier catalogPhysicalName = - sqlStringGenerationContext.catalogWithDefault( namespace.getPhysicalName().getCatalog() ); + Namespace.Name logicalName = namespace.getName(); + Namespace.Name physicalName = namespace.getPhysicalName(); + if ( tryToCreateCatalogs ) { + final Identifier catalogLogicalName = logicalName.getCatalog(); + final Identifier catalogPhysicalName = sqlStringGenerationContext.catalogWithDefault( physicalName.getCatalog() ); if ( catalogPhysicalName != null && !exportedCatalogs.contains( catalogLogicalName ) ) { applySqlStrings( dialect.getCreateCatalogCommand( catalogPhysicalName.render( dialect ) ), @@ -249,15 +250,16 @@ public void createFromMetadata( } } - final Identifier schemaPhysicalName = - sqlStringGenerationContext.schemaWithDefault( namespace.getPhysicalName().getSchema() ); - if ( tryToCreateSchemas && schemaPhysicalName != null ) { - applySqlStrings( - dialect.getCreateSchemaCommand( schemaPhysicalName.render( dialect ) ), - formatter, - options, - targets - ); + if ( tryToCreateSchemas ) { + final Identifier schemaPhysicalName = sqlStringGenerationContext.schemaWithDefault( physicalName.getSchema() ); + if ( schemaPhysicalName != null ) { + applySqlStrings( + dialect.getCreateSchemaCommand( schemaPhysicalName.render( dialect ) ), + formatter, + options, + targets + ); + } } } } diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/SchemaDropperImpl.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/SchemaDropperImpl.java index 753e95f3fd99..6a10079ed131 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/SchemaDropperImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/SchemaDropperImpl.java @@ -275,29 +275,34 @@ private void dropFromMetadata( ); } - if ( tryToDropCatalogs || tryToDropSchemas ) { - Set exportedCatalogs = new HashSet(); - - for ( Namespace namespace : database.getNamespaces() ) { + if ( tryToDropCatalogs || tryToDropSchemas) { + final Set exportedCatalogs = new HashSet(); + for ( Namespace namespace : metadata.getDatabase().getNamespaces() ) { if ( !schemaFilter.includeNamespace( namespace ) ) { continue; } - if ( tryToDropSchemas && namespace.getPhysicalName().getSchema() != null ) { - applySqlStrings( - dialect.getDropSchemaCommand( - namespace.getPhysicalName().getSchema().render( dialect ) - ), - formatter, - options, - targets - ); + Namespace.Name logicalName = namespace.getName(); + Namespace.Name physicalName = namespace.getPhysicalName(); + + if ( tryToDropSchemas ) { + final Identifier schemaPhysicalName = sqlStringGenerationContext.schemaWithDefault( physicalName.getSchema() ); + if ( schemaPhysicalName != null ) { + applySqlStrings( + dialect.getDropSchemaCommand( + schemaPhysicalName.render( dialect ) + ), + formatter, + options, + targets + ); + } } - if ( tryToDropCatalogs ) { - final Identifier catalogLogicalName = namespace.getName().getCatalog(); - final Identifier catalogPhysicalName = namespace.getPhysicalName().getCatalog(); + if (tryToDropCatalogs) { + final Identifier catalogLogicalName = logicalName.getCatalog(); + final Identifier catalogPhysicalName = sqlStringGenerationContext.catalogWithDefault( physicalName.getCatalog() ); if ( catalogPhysicalName != null && !exportedCatalogs.contains( catalogLogicalName ) ) { applySqlStrings( dialect.getDropCatalogCommand( diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/StandardTableExporter.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/StandardTableExporter.java index 8d0edf29617e..e547c3757de6 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/StandardTableExporter.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/StandardTableExporter.java @@ -16,6 +16,7 @@ import org.hibernate.boot.model.relational.InitCommand; import org.hibernate.boot.model.relational.QualifiedName; import org.hibernate.boot.model.relational.QualifiedNameParser; +import org.hibernate.boot.model.relational.QualifiedTableName; import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.dialect.Dialect; import org.hibernate.mapping.Column; @@ -43,10 +44,11 @@ public String[] getSqlCreateStrings(Table table, Metadata metadata, table.getNameIdentifier() ); + String formattedTableName = context.format( tableName ); StringBuilder buf = new StringBuilder( tableCreateString( table.hasPrimaryKey() ) ) .append( ' ' ) - .append( context.format( tableName ) ) + .append( formattedTableName ) .append( " (" ); @@ -145,24 +147,41 @@ public String[] getSqlCreateStrings(Table table, Metadata metadata, List sqlStrings = new ArrayList(); sqlStrings.add( buf.toString() ); - applyComments( table, tableName, sqlStrings ); + applyComments( table, formattedTableName, sqlStrings ); applyInitCommands( table, sqlStrings, context ); return sqlStrings.toArray( new String[ sqlStrings.size() ] ); } - protected void applyComments(Table table, QualifiedName tableName, List sqlStrings) { + /** + * @param table The table. + * @param tableName The qualified table name. + * @param sqlStrings The list of SQL strings to add comments to. + * @deprecated Use {@link #applyComments(Table, String, List)} instead. + */ + // For backwards compatibility with subclasses that happen to call this method... + @Deprecated + protected void applyComments(Table table, QualifiedTableName tableName, List sqlStrings) { + applyComments( table, tableName.toString(), sqlStrings ); + } + + /** + * @param table The table. + * @param formattedTableName The formatted table name. + * @param sqlStrings The list of SQL strings to add comments to. + */ + protected void applyComments(Table table, String formattedTableName, List sqlStrings) { if ( dialect.supportsCommentOn() ) { if ( table.getComment() != null ) { - sqlStrings.add( "comment on table " + tableName + " is '" + table.getComment() + "'" ); + sqlStrings.add( "comment on table " + formattedTableName + " is '" + table.getComment() + "'" ); } final Iterator iter = table.getColumnIterator(); while ( iter.hasNext() ) { Column column = (Column) iter.next(); String columnComment = column.getComment(); if ( columnComment != null ) { - sqlStrings.add( "comment on column " + tableName + '.' + column.getQuotedName( dialect ) + " is '" + columnComment + "'" ); + sqlStrings.add( "comment on column " + formattedTableName + '.' + column.getQuotedName( dialect ) + " is '" + columnComment + "'" ); } } } diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/spi/SchemaManagementToolCoordinator.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/spi/SchemaManagementToolCoordinator.java index 4c0c33807cb9..bd9f662dc8e5 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/spi/SchemaManagementToolCoordinator.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/spi/SchemaManagementToolCoordinator.java @@ -7,7 +7,9 @@ package org.hibernate.tool.schema.spi; import java.util.EnumSet; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import org.hibernate.boot.Metadata; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; @@ -41,6 +43,8 @@ import static org.hibernate.cfg.AvailableSettings.JAKARTA_HBM2DDL_SCRIPTS_ACTION; import static org.hibernate.cfg.AvailableSettings.JAKARTA_HBM2DDL_SCRIPTS_CREATE_TARGET; import static org.hibernate.cfg.AvailableSettings.JAKARTA_HBM2DDL_SCRIPTS_DROP_TARGET; +import static org.hibernate.internal.log.DeprecationLogger.DEPRECATION_LOGGER; +import static org.hibernate.internal.util.NullnessHelper.coalesceSuppliedValues; /** * Responsible for coordinating SchemaManagementTool execution(s) for auto-tooling whether @@ -527,27 +531,69 @@ public Action getScriptAction() { } public static ActionGrouping interpret(Map configurationValues) { - Object databaseActionSetting = configurationValues.get( HBM2DDL_DATABASE_ACTION ); - Object scriptsActionSetting = configurationValues.get( HBM2DDL_SCRIPTS_ACTION ); - if ( databaseActionSetting == null ) { - databaseActionSetting = configurationValues.get( JAKARTA_HBM2DDL_DATABASE_ACTION ); - } - if ( scriptsActionSetting == null ) { - scriptsActionSetting = configurationValues.get( JAKARTA_HBM2DDL_SCRIPTS_ACTION ); - } - // interpret the JPA settings first - Action databaseAction = Action.interpretJpaSetting( databaseActionSetting ); - Action scriptAction = Action.interpretJpaSetting( scriptsActionSetting ); - - // if no JPA settings were specified, look at the legacy HBM2DDL_AUTO setting... - if ( databaseAction == Action.NONE && scriptAction == Action.NONE ) { - final Action hbm2ddlAutoAction = Action.interpretHbm2ddlSetting( configurationValues.get( HBM2DDL_AUTO ) ); - if ( hbm2ddlAutoAction != Action.NONE ) { - databaseAction = hbm2ddlAutoAction; + // default to the JPA settings + Action databaseActionToUse = determineJpaDbActionSetting( configurationValues ); + Action scriptActionToUse = determineJpaScriptActionSetting( configurationValues ); + Action autoAction = determineAutoSettingImpliedAction( configurationValues, null ); + + if ( databaseActionToUse == null && scriptActionToUse == null ) { + // no JPA (jakarta nor javax) settings were specified, use the legacy Hibernate + // `hbm2ddl.auto` setting to possibly set the database-action + if ( autoAction != null ) { + databaseActionToUse = autoAction; } } - return new ActionGrouping( databaseAction, scriptAction ); + if ( databaseActionToUse == null ) { + databaseActionToUse = Action.NONE; + } + + if ( scriptActionToUse == null ) { + scriptActionToUse = Action.NONE; + } + + if ( databaseActionToUse == Action.NONE && scriptActionToUse == Action.NONE ) { + log.debugf( "No schema actions specified" ); + } + + return new ActionGrouping( databaseActionToUse, scriptActionToUse ); + } + + private static Action determineJpaDbActionSetting(Map configurationValues) { + final Object scriptsActionSetting = coalesceSuppliedValues( + () -> configurationValues.get( JAKARTA_HBM2DDL_DATABASE_ACTION ), + () -> { + final Object setting = configurationValues.get( HBM2DDL_DATABASE_ACTION ); + //Not using the DEPRECATION_LOGGER as while this branch understands Jakarta configuration, + //it's not meant to be the primary one yet. + return setting; + } + ); + + return scriptsActionSetting == null ? null : Action.interpretJpaSetting( scriptsActionSetting ); + } + + private static Action determineJpaScriptActionSetting(Map configurationValues) { + final Object scriptsActionSetting = coalesceSuppliedValues( + () -> configurationValues.get( JAKARTA_HBM2DDL_SCRIPTS_ACTION ), + () -> { + final Object setting = configurationValues.get( HBM2DDL_SCRIPTS_ACTION ); + //Not using the DEPRECATION_LOGGER as while this branch understands Jakarta configuration, + //it's not meant to be the primary one yet. + return setting; + } + ); + + return scriptsActionSetting == null ? null : Action.interpretJpaSetting( scriptsActionSetting ); + } + + public static Action determineAutoSettingImpliedAction(Map settings, Action defaultValue) { + final Object autoActionSetting = settings.get( HBM2DDL_AUTO ); + if ( autoActionSetting == null ) { + return defaultValue; + } + + return Action.interpretHbm2ddlSetting( autoActionSetting ); } } } 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 3d4ea08a4716..c7bce2c45bf1 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 @@ -19,6 +19,7 @@ import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributesMetadata; import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; +import org.hibernate.engine.internal.ManagedTypeHelper; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.PersistenceContext; @@ -272,7 +273,11 @@ else if ( identifierMapperType != null ) { private static interface MappedIdentifierValueMarshaller { public Object getIdentifier(Object entity, EntityMode entityMode, SharedSessionContractImplementor session); - public void setIdentifier(Object entity, Serializable id, EntityMode entityMode, SharedSessionContractImplementor session); + public void setIdentifier( + Object entity, + Serializable id, + EntityMode entityMode, + SharedSessionContractImplementor session); } private final MappedIdentifierValueMarshaller mappedIdentifierValueMarshaller; @@ -312,7 +317,7 @@ private static MappedIdentifierValueMarshaller buildMappedIdentifierValueMarshal virtualIdComponent, mappedIdClassComponentType, identifier - ); + ); } private static class NormalMappedIdentifierValueMarshaller implements MappedIdentifierValueMarshaller { @@ -335,7 +340,11 @@ public Object getIdentifier(Object entity, EntityMode entityMode, SharedSessionC } @Override - public void setIdentifier(Object entity, Serializable id, EntityMode entityMode, SharedSessionContractImplementor session) { + public void setIdentifier( + Object entity, + Serializable id, + EntityMode entityMode, + SharedSessionContractImplementor session) { virtualIdComponent.setPropertyValues( entity, mappedIdentifierType.getPropertyValues( id, session ), @@ -385,7 +394,7 @@ public Object getIdentifier(Object entity, EntityMode entityMode, SharedSessionC } } //JPA 2 @MapsId + @IdClass points to the pk of the entity - if ( subType.isAssociationType() && !copierSubTypes[i].isAssociationType() ) { + if ( subType.isAssociationType() && !copierSubTypes[i].isAssociationType() ) { propertyValues[i] = determineEntityId( propertyValues[i], (AssociationType) subType, @@ -399,7 +408,11 @@ public Object getIdentifier(Object entity, EntityMode entityMode, SharedSessionC } @Override - public void setIdentifier(Object entity, Serializable id, EntityMode entityMode, SharedSessionContractImplementor session) { + public void setIdentifier( + Object entity, + Serializable id, + EntityMode entityMode, + SharedSessionContractImplementor session) { final Object[] extractedValues = mappedIdentifierType.getPropertyValues( id, entityMode ); final Object[] injectionValues = new Object[extractedValues.length]; final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); @@ -569,8 +582,8 @@ public Object[] getPropertyValues(Object entity) { // 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) ) { + if ( !lazyAttributesMetadata.isLazyAttribute( propertyName ) + || enhancementMetadata.isAttributeLoaded( entity, propertyName ) ) { result[j] = getters[j].get( entity ); } else { @@ -714,11 +727,14 @@ protected void linkToSession(Object entity, SharedSessionContractImplementor ses if ( session == null ) { return; } - if ( entity instanceof PersistentAttributeInterceptable ) { - final BytecodeLazyAttributeInterceptor interceptor = getEntityMetamodel().getBytecodeEnhancementMetadata().extractLazyInterceptor( entity ); - if ( interceptor != null ) { - interceptor.setSession( session ); - } + ManagedTypeHelper.processIfPersistentAttributeInterceptable( entity, this::setSession, session ); + } + + private void setSession(PersistentAttributeInterceptable entity, SharedSessionContractImplementor session) { + final BytecodeLazyAttributeInterceptor interceptor = getEntityMetamodel().getBytecodeEnhancementMetadata() + .extractLazyInterceptor( entity ); + if ( interceptor != null ) { + interceptor.setSession( session ); } } 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 8f4eec445da5..a5cc6151cac2 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 @@ -16,6 +16,7 @@ import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributesMetadata; import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; import org.hibernate.bytecode.spi.NotInstrumentedException; +import org.hibernate.engine.internal.ManagedTypeHelper; import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.PersistentAttributeInterceptable; @@ -148,9 +149,8 @@ public PersistentAttributeInterceptable createEnhancedProxy(EntityKey entityKey, .instantiate( identifier, session ); // clear the fields that are marked as dirty in the dirtiness tracker - if ( entity instanceof SelfDirtinessTracker ) { - ( (SelfDirtinessTracker) entity ).$$_hibernate_clearDirtyAttributes(); - } + ManagedTypeHelper.processIfSelfDirtinessTracker( entity, SelfDirtinessTracker::$$_hibernate_clearDirtyAttributes ); + // add the entity (proxy) instance to the PC persistenceContext.addEnhancedProxy( entityKey, entity ); @@ -268,7 +268,7 @@ public BytecodeLazyAttributeInterceptor extractLazyInterceptor(Object entity) th ); } - final PersistentAttributeInterceptor interceptor = ( (PersistentAttributeInterceptable) entity ).$$_hibernate_getInterceptor(); + final PersistentAttributeInterceptor interceptor = ManagedTypeHelper.asPersistentAttributeInterceptable( entity ).$$_hibernate_getInterceptor(); if ( interceptor == null ) { 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 0910b6d9aea3..4826a1e26737 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 @@ -6,12 +6,10 @@ */ package org.hibernate.tuple.entity; -import org.hibernate.PropertyNotFoundException; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; import org.hibernate.bytecode.spi.ReflectionOptimizer; -import org.hibernate.engine.spi.PersistentAttributeInterceptable; +import org.hibernate.engine.internal.ManagedTypeHelper; import org.hibernate.engine.spi.PersistentAttributeInterceptor; -import org.hibernate.internal.util.ReflectHelper; import org.hibernate.mapping.PersistentClass; import org.hibernate.tuple.PojoInstantiator; @@ -35,7 +33,9 @@ public PojoEntityInstantiator( this.entityMetamodel = entityMetamodel; this.proxyInterface = persistentClass.getProxyInterface(); - this.applyBytecodeInterception = PersistentAttributeInterceptable.class.isAssignableFrom( persistentClass.getMappedClass() ); + + //TODO this PojoEntityInstantiator appears to not be reused ?! + this.applyBytecodeInterception = ManagedTypeHelper.isPersistentAttributeInterceptableType( persistentClass.getMappedClass() ); } @Override @@ -52,7 +52,7 @@ protected Object applyInterception(Object entity) { .getLazyAttributeNames(), null ); - ( (PersistentAttributeInterceptable) entity ).$$_hibernate_setInterceptor( interceptor ); + ManagedTypeHelper.asPersistentAttributeInterceptable( entity ).$$_hibernate_setInterceptor( interceptor ); return entity; } 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 ef6aaf97a13b..627ed6174df2 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 @@ -20,7 +20,7 @@ import org.hibernate.bytecode.spi.ReflectionOptimizer; import org.hibernate.cfg.Environment; import org.hibernate.classic.Lifecycle; -import org.hibernate.engine.spi.PersistentAttributeInterceptable; +import org.hibernate.engine.internal.ManagedTypeHelper; import org.hibernate.engine.spi.SelfDirtinessTracker; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; @@ -207,7 +207,8 @@ public Class getConcreteProxyClass() { @Override public void afterInitialize(Object entity, SharedSessionContractImplementor session) { - if ( entity instanceof PersistentAttributeInterceptable ) { + //type-cache-pollution agent: always check for EnhancedEntity type first. + if ( ManagedTypeHelper.isPersistentAttributeInterceptable( entity ) ) { final BytecodeLazyAttributeInterceptor interceptor = getEntityMetamodel().getBytecodeEnhancementMetadata().extractLazyInterceptor( entity ); if ( interceptor == null || interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { getEntityMetamodel().getBytecodeEnhancementMetadata().injectInterceptor( @@ -224,9 +225,11 @@ public void afterInitialize(Object entity, SharedSessionContractImplementor sess } // clear the fields that are marked as dirty in the dirtiness tracker - if ( entity instanceof SelfDirtinessTracker ) { - ( (SelfDirtinessTracker) entity ).$$_hibernate_clearDirtyAttributes(); - } + ManagedTypeHelper.processIfSelfDirtinessTracker( entity, PojoEntityTuplizer::clearDirtyAttributes ); + } + + private static void clearDirtyAttributes(final SelfDirtinessTracker entity) { + entity.$$_hibernate_clearDirtyAttributes(); } @Override 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 e9f4583a696a..fc761f539572 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/EntityType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/EntityType.java @@ -16,6 +16,7 @@ import org.hibernate.AssertionFailure; import org.hibernate.HibernateException; import org.hibernate.MappingException; +import org.hibernate.annotations.NotFoundAction; import org.hibernate.engine.internal.ForeignKeys; import org.hibernate.engine.spi.EntityUniqueKey; import org.hibernate.engine.spi.Mapping; @@ -680,6 +681,12 @@ public boolean isReferenceToIdentifierProperty() { */ public abstract boolean isNullable(); + public abstract NotFoundAction getNotFoundAction(); + + public boolean hasNotFoundAction() { + return getNotFoundAction() != null; + } + /** * 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 c6be39af5eff..79ab72907452 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/ManyToOneType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/ManyToOneType.java @@ -12,11 +12,19 @@ import java.util.Arrays; import org.hibernate.AssertionFailure; +import org.hibernate.FetchNotFoundException; import org.hibernate.HibernateException; import org.hibernate.MappingException; +import org.hibernate.ObjectNotFoundException; +import org.hibernate.annotations.NotFoundAction; import org.hibernate.engine.internal.ForeignKeys; import org.hibernate.engine.jdbc.Size; -import org.hibernate.engine.spi.*; +import org.hibernate.engine.spi.EntityEntry; +import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.EntityUniqueKey; +import org.hibernate.engine.spi.Mapping; +import org.hibernate.engine.spi.PersistenceContext; +import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.Loadable; @@ -27,7 +35,7 @@ */ public class ManyToOneType extends EntityType { private final String propertyName; - private final boolean ignoreNotFound; + private final NotFoundAction notFoundAction; private boolean isLogicalOneToOne; /** @@ -49,12 +57,12 @@ public ManyToOneType(TypeFactory.TypeScope scope, String referencedEntityName) { * @param lazy Should the association be handled lazily */ public ManyToOneType(TypeFactory.TypeScope scope, String referencedEntityName, boolean lazy) { - this( scope, referencedEntityName, true, null, lazy, true, false, false ); + this( scope, referencedEntityName, true, null, lazy, true, null, false ); } /** - * @deprecated Use {@link #ManyToOneType(TypeFactory.TypeScope, String, boolean, String, String, boolean, boolean, boolean, boolean ) } instead. + * @deprecated Use {@link #ManyToOneType(TypeFactory.TypeScope, String, boolean, String, String, boolean, boolean, NotFoundAction, boolean ) } instead. */ @Deprecated public ManyToOneType( @@ -64,13 +72,13 @@ public ManyToOneType( boolean lazy, boolean unwrapProxy, boolean isEmbeddedInXML, - boolean ignoreNotFound, + NotFoundAction notFoundAction, boolean isLogicalOneToOne) { - this( scope, referencedEntityName, uniqueKeyPropertyName == null, uniqueKeyPropertyName, lazy, unwrapProxy, ignoreNotFound, isLogicalOneToOne ); + this( scope, referencedEntityName, uniqueKeyPropertyName == null, uniqueKeyPropertyName, lazy, unwrapProxy, notFoundAction, isLogicalOneToOne ); } /** - * @deprecated Use {@link #ManyToOneType(TypeFactory.TypeScope, String, boolean, String, String, boolean, boolean, boolean, boolean ) } instead. + * @deprecated Use {@link #ManyToOneType(TypeFactory.TypeScope, String, boolean, String, String, boolean, boolean, NotFoundAction, boolean ) } instead. */ @Deprecated public ManyToOneType( @@ -80,9 +88,19 @@ public ManyToOneType( String uniqueKeyPropertyName, boolean lazy, boolean unwrapProxy, - boolean ignoreNotFound, + NotFoundAction notFoundAction, boolean isLogicalOneToOne) { - this( scope, referencedEntityName, referenceToPrimaryKey, uniqueKeyPropertyName, null, lazy, unwrapProxy, ignoreNotFound, isLogicalOneToOne ); + this( + scope, + referencedEntityName, + referenceToPrimaryKey, + uniqueKeyPropertyName, + null, + lazy, + unwrapProxy, + notFoundAction, + isLogicalOneToOne + ); } public ManyToOneType( @@ -93,24 +111,29 @@ public ManyToOneType( String propertyName, boolean lazy, boolean unwrapProxy, - boolean ignoreNotFound, + NotFoundAction notFoundAction, boolean isLogicalOneToOne) { super( scope, referencedEntityName, referenceToPrimaryKey, uniqueKeyPropertyName, !lazy, unwrapProxy ); this.propertyName = propertyName; - this.ignoreNotFound = ignoreNotFound; + this.notFoundAction = notFoundAction; this.isLogicalOneToOne = isLogicalOneToOne; } public ManyToOneType(ManyToOneType original, String superTypeEntityName) { super( original, superTypeEntityName ); this.propertyName = original.propertyName; - this.ignoreNotFound = original.ignoreNotFound; + this.notFoundAction = original.notFoundAction; this.isLogicalOneToOne = original.isLogicalOneToOne; } @Override public boolean isNullable() { - return ignoreNotFound; + return notFoundAction != null; + } + + @Override + public NotFoundAction getNotFoundAction() { + return notFoundAction; } @Override @@ -237,7 +260,20 @@ public boolean isModified( @Override public Object resolve(Object value, SharedSessionContractImplementor session, Object owner, Boolean overridingEager) throws HibernateException { - Object resolvedValue = super.resolve(value, session, owner, overridingEager); + final Object resolvedValue; + try { + resolvedValue = super.resolve( value, session, owner, overridingEager ); + } + catch (ObjectNotFoundException e) { + throw new FetchNotFoundException( getAssociatedEntityName(), value ); + } + + if ( value != null + && resolvedValue == null + && getNotFoundAction() == NotFoundAction.EXCEPTION ) { + throw new FetchNotFoundException( getAssociatedEntityName(), value ); + } + if ( isLogicalOneToOne && value != null && getPropertyName() != null ) { final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); EntityEntry entry = persistenceContext.getEntry( owner ); 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 07311e32bf10..9ec9c3fd445f 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/OneToOneType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/OneToOneType.java @@ -14,6 +14,7 @@ import org.hibernate.AssertionFailure; import org.hibernate.HibernateException; import org.hibernate.MappingException; +import org.hibernate.annotations.NotFoundAction; import org.hibernate.engine.internal.ForeignKeys; import org.hibernate.engine.jdbc.Size; import org.hibernate.engine.spi.EntityKey; @@ -151,19 +152,12 @@ public boolean isOneToOne() { @Override public boolean isDirty(Object old, Object current, SharedSessionContractImplementor session) { - if ( isSame( old, current ) ) { - return false; - } - - Object oldid = getIdentifier( old, session ); - Object newid = getIdentifier( current, session ); - - return getIdentifierType( session ).isDirty( oldid, newid, session ); + return false; } @Override public boolean isDirty(Object old, Object current, boolean[] checkable, SharedSessionContractImplementor session) { - return isDirty(old, current, session); + return false; } @Override @@ -190,6 +184,11 @@ public boolean isNullable() { return !constrained; } + @Override + public NotFoundAction getNotFoundAction() { + return null; + } + @Override public boolean useLHSPrimaryKey() { return true; @@ -197,36 +196,25 @@ public boolean useLHSPrimaryKey() { @Override public Serializable disassemble(Object value, SharedSessionContractImplementor session, Object owner) throws HibernateException { - if (value == null) { - return null; - } - - Object id = ForeignKeys.getEntityIdentifierIfNotUnsaved( getAssociatedEntityName(), value, session ); - - if ( id == null ) { - throw new AssertionFailure( - "cannot cache a reference to an object with a null id: " + - getAssociatedEntityName() - ); - } - - return getIdentifierType( session ).disassemble( id, session, owner ); + return null; } @Override public Object assemble(Serializable oid, SharedSessionContractImplementor session, Object owner) throws HibernateException { - //the owner of the association is not the owner of the id - Serializable id = ( Serializable ) getIdentifierType( session ).assemble( oid, session, null ); - - if ( id == null ) { - return null; - } - - return resolveIdentifier( id, session ); + //this should be a call to resolve(), not resolveIdentifier(), + //because it might be a property-ref, and we did not cache the + //referenced value + return resolve( session.getContextEntityIdentifier(owner), session, owner ); } + /** + * We don't need to dirty check one-to-one because of how + * assemble/disassemble is implemented and because a one-to-one + * association is never dirty + */ @Override public boolean isAlwaysDirtyChecked() { - return true; + //TODO: this is kinda inconsistent with CollectionType + return false; } } 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 9acb4bc484a5..b83d719d3fe1 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/TypeFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/type/TypeFactory.java @@ -13,6 +13,7 @@ import java.util.Properties; import org.hibernate.MappingException; +import org.hibernate.annotations.NotFoundAction; import org.hibernate.classic.Lifecycle; import org.hibernate.engine.config.spi.ConfigurationService; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -41,7 +42,7 @@ @SuppressWarnings({"unchecked"}) public final class TypeFactory implements Serializable, TypeBootstrapContext { /** - * @deprecated Use {@link TypeConfiguration}/{@link TypeConfiguration.Scope} instead + * @deprecated Use {@link TypeConfiguration} */ @Deprecated public interface TypeScope extends Serializable { @@ -295,7 +296,7 @@ public EntityType manyToOne(String persistentClass, boolean lazy) { } /** - * @deprecated Use {@link #manyToOne(String, boolean, String, boolean, boolean, boolean, boolean)} instead. + * @deprecated Use {@link #manyToOne(String, boolean, String, boolean, boolean, NotFoundAction, boolean)} instead. */ @Deprecated public EntityType manyToOne( @@ -303,7 +304,7 @@ public EntityType manyToOne( String uniqueKeyPropertyName, boolean lazy, boolean unwrapProxy, - boolean ignoreNotFound, + NotFoundAction notFoundAction, boolean isLogicalOneToOne) { return manyToOne( persistentClass, @@ -311,13 +312,13 @@ public EntityType manyToOne( uniqueKeyPropertyName, lazy, unwrapProxy, - ignoreNotFound, + notFoundAction, isLogicalOneToOne ); } /** - * @deprecated Use {@link #manyToOne(String, boolean, String, String, boolean, boolean, boolean, boolean)} instead. + * @deprecated Use {@link #manyToOne(String, boolean, String, String, boolean, boolean, NotFoundAction, boolean)} instead. */ @Deprecated public EntityType manyToOne( @@ -326,7 +327,7 @@ public EntityType manyToOne( String uniqueKeyPropertyName, boolean lazy, boolean unwrapProxy, - boolean ignoreNotFound, + NotFoundAction notFoundAction, boolean isLogicalOneToOne) { return manyToOne( persistentClass, @@ -335,7 +336,7 @@ public EntityType manyToOne( null, lazy, unwrapProxy, - ignoreNotFound, + notFoundAction, isLogicalOneToOne ); } @@ -347,7 +348,7 @@ public EntityType manyToOne( String propertyName, boolean lazy, boolean unwrapProxy, - boolean ignoreNotFound, + NotFoundAction notFoundAction, boolean isLogicalOneToOne) { return new ManyToOneType( typeScope, @@ -357,7 +358,7 @@ public EntityType manyToOne( propertyName, lazy, unwrapProxy, - ignoreNotFound, + notFoundAction, isLogicalOneToOne ); } 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 4e34b1bfcdd9..38c7324f057a 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/TypeHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/TypeHelper.java @@ -6,6 +6,7 @@ */ package org.hibernate.type; +import org.hibernate.EntityMode; import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.util.collections.ArrayHelper; @@ -245,6 +246,19 @@ else if ( types[i].isComponentType() ) { Object[] origComponentValues = original[i] == null ? new Object[subtypes.length] : componentType.getPropertyValues( original[i], session ); Object[] targetComponentValues = target[i] == null ? new Object[subtypes.length] : componentType.getPropertyValues( target[i], session ); replaceAssociations( origComponentValues, targetComponentValues, subtypes, session, null, copyCache, foreignKeyDirection ); + final Object[] objects = replaceAssociations( + origComponentValues, + targetComponentValues, + subtypes, + session, + null, + copyCache, + foreignKeyDirection + ); + if ( componentType.isMutable() && target[i] != null && objects != null ) { + // Need to account for entity mode on the CompositeType interface, that seems not been used by any implementation + componentType.setPropertyValues( target[i], objects, EntityMode.POJO ); + } copied[i] = target[i]; } else if ( !types[i].isAssociationType() ) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/UUIDTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/UUIDTypeDescriptor.java index 694a5012b76c..30f8fe7b62f5 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/UUIDTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/UUIDTypeDescriptor.java @@ -7,10 +7,13 @@ package org.hibernate.type.descriptor.java; import java.io.Serializable; +import java.sql.Types; import java.util.UUID; import org.hibernate.internal.util.BytesHelper; import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.spi.JdbcRecommendedSqlTypeMappingContext; +import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; /** * Descriptor for {@link UUID} handling. @@ -32,6 +35,11 @@ public UUID fromString(String string) { return ToStringTransformer.INSTANCE.parse( string ); } + @Override + public SqlTypeDescriptor getJdbcRecommendedSqlType(JdbcRecommendedSqlTypeMappingContext context) { + return context.getTypeConfiguration().getSqlTypeDescriptorRegistry().getDescriptor( Types.VARCHAR ); + } + @SuppressWarnings({ "unchecked" }) public X unwrap(UUID value, Class type, WrapperOptions options) { if ( value == null ) { diff --git a/hibernate-core/src/test/bundles/templates/cfgxmlpar/org/hibernate/jpa/test/pack/cfgxmlpar/hibernate.cfg.xml b/hibernate-core/src/test/bundles/templates/cfgxmlpar/org/hibernate/jpa/test/pack/cfgxmlpar/hibernate.cfg.xml index 31857a6a0174..7ae30207f756 100644 --- a/hibernate-core/src/test/bundles/templates/cfgxmlpar/org/hibernate/jpa/test/pack/cfgxmlpar/hibernate.cfg.xml +++ b/hibernate-core/src/test/bundles/templates/cfgxmlpar/org/hibernate/jpa/test/pack/cfgxmlpar/hibernate.cfg.xml @@ -14,6 +14,7 @@ org.h2.Driver sa + jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1 true hibernate.test diff --git a/hibernate-core/src/test/bundles/templates/defaultpar/META-INF/persistence.xml b/hibernate-core/src/test/bundles/templates/defaultpar/META-INF/persistence.xml index a9e31e39bfdb..72ae3cdd598b 100644 --- a/hibernate-core/src/test/bundles/templates/defaultpar/META-INF/persistence.xml +++ b/hibernate-core/src/test/bundles/templates/defaultpar/META-INF/persistence.xml @@ -20,6 +20,7 @@ + diff --git a/hibernate-core/src/test/bundles/templates/defaultpar_1_0/META-INF/persistence.xml b/hibernate-core/src/test/bundles/templates/defaultpar_1_0/META-INF/persistence.xml index c0cdcc9abde0..c754da627e74 100644 --- a/hibernate-core/src/test/bundles/templates/defaultpar_1_0/META-INF/persistence.xml +++ b/hibernate-core/src/test/bundles/templates/defaultpar_1_0/META-INF/persistence.xml @@ -19,6 +19,7 @@ + diff --git a/hibernate-core/src/test/bundles/templates/excludehbmpar/META-INF/persistence.xml b/hibernate-core/src/test/bundles/templates/excludehbmpar/META-INF/persistence.xml index a98d66020c34..a7773844b69f 100644 --- a/hibernate-core/src/test/bundles/templates/excludehbmpar/META-INF/persistence.xml +++ b/hibernate-core/src/test/bundles/templates/excludehbmpar/META-INF/persistence.xml @@ -19,6 +19,7 @@ + diff --git a/hibernate-core/src/test/bundles/templates/explicitpar/META-INF/persistence.xml b/hibernate-core/src/test/bundles/templates/explicitpar/META-INF/persistence.xml index ba220a889d4e..8512f8ea15b1 100644 --- a/hibernate-core/src/test/bundles/templates/explicitpar/META-INF/persistence.xml +++ b/hibernate-core/src/test/bundles/templates/explicitpar/META-INF/persistence.xml @@ -25,6 +25,7 @@ + diff --git a/hibernate-core/src/test/bundles/templates/explicitpar2/META-INF/persistence.xml b/hibernate-core/src/test/bundles/templates/explicitpar2/META-INF/persistence.xml index 6dbad8251482..6763c7d9d23c 100644 --- a/hibernate-core/src/test/bundles/templates/explicitpar2/META-INF/persistence.xml +++ b/hibernate-core/src/test/bundles/templates/explicitpar2/META-INF/persistence.xml @@ -28,6 +28,7 @@ + diff --git a/hibernate-core/src/test/bundles/templates/explodedpar/META-INF/persistence.xml b/hibernate-core/src/test/bundles/templates/explodedpar/META-INF/persistence.xml index e73198f56502..8a2291d6a964 100644 --- a/hibernate-core/src/test/bundles/templates/explodedpar/META-INF/persistence.xml +++ b/hibernate-core/src/test/bundles/templates/explodedpar/META-INF/persistence.xml @@ -18,6 +18,7 @@ + diff --git a/hibernate-core/src/test/bundles/templates/overridenpar/overridenpar.properties b/hibernate-core/src/test/bundles/templates/overridenpar/overridenpar.properties index 415ae95c8a01..dfe6e40ca3d9 100644 --- a/hibernate-core/src/test/bundles/templates/overridenpar/overridenpar.properties +++ b/hibernate-core/src/test/bundles/templates/overridenpar/overridenpar.properties @@ -7,4 +7,5 @@ hibernate.connection.driver_class @jdbc.driver@ hibernate.connection.url @jdbc.url@ hibernate.connection.username @jdbc.user@ -hibernate.connection.password @jdbc.pass@ \ No newline at end of file +hibernate.connection.password @jdbc.pass@ +hibernate.connection.init_sql @connection.init_sql@ \ No newline at end of file diff --git a/hibernate-core/src/test/bundles/templates/space par/META-INF/persistence.xml b/hibernate-core/src/test/bundles/templates/space par/META-INF/persistence.xml index d4055de74136..de12ae6befa9 100644 --- a/hibernate-core/src/test/bundles/templates/space par/META-INF/persistence.xml +++ b/hibernate-core/src/test/bundles/templates/space par/META-INF/persistence.xml @@ -18,6 +18,7 @@ + diff --git a/hibernate-core/src/test/bundles/templates/war/WEB-INF/classes/META-INF/persistence.xml b/hibernate-core/src/test/bundles/templates/war/WEB-INF/classes/META-INF/persistence.xml index 784cb33395d1..4f61dc82a138 100644 --- a/hibernate-core/src/test/bundles/templates/war/WEB-INF/classes/META-INF/persistence.xml +++ b/hibernate-core/src/test/bundles/templates/war/WEB-INF/classes/META-INF/persistence.xml @@ -19,6 +19,7 @@ + diff --git a/hibernate-core/src/test/java/org/hibernate/boot/jaxb/internal/stax/LocalXmlResourceResolverTest.java b/hibernate-core/src/test/java/org/hibernate/boot/jaxb/internal/stax/LocalXmlResourceResolverTest.java new file mode 100644 index 000000000000..57c7d0d41386 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/boot/jaxb/internal/stax/LocalXmlResourceResolverTest.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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.boot.jaxb.internal.stax; + +import static org.assertj.core.api.Assertions.assertThat; + +import javax.xml.stream.XMLStreamException; + +import org.hibernate.testing.boot.ClassLoaderServiceTestingImpl; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import org.assertj.core.api.InstanceOfAssertFactories; + +/** + * Test the resolution of known XML schemas/DTDs to local resources. + *

      + * Note that when it comes to XML schemas, + * LocalXmlResourceResolver doesn't seem to be actually invoked; + * which makes sense since we set the XML schema ourselves when configuring the parser. + * So this test is probably only relevant for DTDs, but we keep tests about XML schemas too just in case. + */ +public class LocalXmlResourceResolverTest { + + private final LocalXmlResourceResolver resolver; + + public LocalXmlResourceResolverTest() { + this.resolver = new LocalXmlResourceResolver( ClassLoaderServiceTestingImpl.INSTANCE ); + } + + @ParameterizedTest + @CsvSource({ + // JPA 1.0 and 2.0 share the same namespace URI + // NOTE: Behavior differs from Hibernate ORM 6, which resolves to org/hibernate/jpa/orm_1_0.xsd + "http://java.sun.com/xml/ns/persistence/orm,org/hibernate/jpa/orm_2_0.xsd", + // JPA 2.1 and 2.2 share the same namespace URI + "http://xmlns.jcp.org/xml/ns/persistence/orm,org/hibernate/jpa/orm_2_1.xsd", + "https://jakarta.ee/xml/ns/persistence/orm,org/hibernate/jpa/orm_3_0.xsd", + + // NOTE: Hibernate ORM 5 doesn't resolve persistence.xml XSDs to local resources + // so we don't test them here. + + "http://www.hibernate.org/xsd/orm/hbm,org/hibernate/xsd/mapping/legacy-mapping-4.0.xsd", + "http://www.hibernate.org/xsd/hibernate-mapping,org/hibernate/hibernate-mapping-4.0.xsd", + "http://www.hibernate.org/xsd/orm/cfg,org/hibernate/xsd/cfg/legacy-configuration-4.0.xsd", + }) + void resolve_namespace_localResource(String namespace, String expectedLocalResource) throws XMLStreamException { + assertThat( resolver.resolveEntity( null, null, null, namespace ) ) + .asInstanceOf( InstanceOfAssertFactories.INPUT_STREAM ) + .hasSameContentAs( getClass().getClassLoader().getResourceAsStream( expectedLocalResource ) ); + } + + @ParameterizedTest + @CsvSource({ + "http://www.hibernate.org/dtd/hibernate-mapping,org/hibernate/hibernate-mapping-3.0.dtd", + "https://www.hibernate.org/dtd/hibernate-mapping,org/hibernate/hibernate-mapping-3.0.dtd", + + "http://hibernate.org/dtd/hibernate-mapping,org/hibernate/hibernate-mapping-3.0.dtd", + "https://hibernate.org/dtd/hibernate-mapping,org/hibernate/hibernate-mapping-3.0.dtd", + + "http://hibernate.sourceforge.net/hibernate-mapping,org/hibernate/hibernate-mapping-3.0.dtd", + "https://hibernate.sourceforge.net/hibernate-mapping,org/hibernate/hibernate-mapping-3.0.dtd", + + "http://www.hibernate.org/dtd/hibernate-configuration,org/hibernate/hibernate-configuration-3.0.dtd", + "https://www.hibernate.org/dtd/hibernate-configuration,org/hibernate/hibernate-configuration-3.0.dtd", + + "http://hibernate.org/dtd/hibernate-configuration,org/hibernate/hibernate-configuration-3.0.dtd", + "https://hibernate.org/dtd/hibernate-configuration,org/hibernate/hibernate-configuration-3.0.dtd", + + "http://hibernate.sourceforge.net/hibernate-configuration,org/hibernate/hibernate-configuration-3.0.dtd", + "https://hibernate.sourceforge.net/hibernate-configuration,org/hibernate/hibernate-configuration-3.0.dtd", + + "http://hibernate.org/dtd/hibernate-mapping-3.0.dtd,org/hibernate/hibernate-mapping-3.0.dtd", + "https://hibernate.org/dtd/hibernate-mapping-3.0.dtd,org/hibernate/hibernate-mapping-3.0.dtd" + }) + void resolve_dtd_localResource(String id, String expectedLocalResource) throws XMLStreamException { + // publicId + assertThat( resolver.resolveEntity( id, null, null, null ) ) + .asInstanceOf( InstanceOfAssertFactories.INPUT_STREAM ) + .hasSameContentAs( getClass().getClassLoader().getResourceAsStream( expectedLocalResource ) ); + + // systemId + assertThat( resolver.resolveEntity( null, id, null, null ) ) + .asInstanceOf( InstanceOfAssertFactories.INPUT_STREAM ) + .hasSameContentAs( getClass().getClassLoader().getResourceAsStream( expectedLocalResource ) ); + } + +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/cache/spi/ReadWriteCacheTest.java b/hibernate-core/src/test/java/org/hibernate/cache/spi/ReadWriteCacheTest.java index 0eff288506fa..a8991139c42e 100644 --- a/hibernate-core/src/test/java/org/hibernate/cache/spi/ReadWriteCacheTest.java +++ b/hibernate-core/src/test/java/org/hibernate/cache/spi/ReadWriteCacheTest.java @@ -20,6 +20,8 @@ import org.hibernate.cfg.Configuration; import org.hibernate.dialect.CockroachDB192Dialect; +import org.hibernate.dialect.SybaseASE15Dialect; + import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; @@ -63,6 +65,7 @@ public void rebuildSessionFactory() { @Test @SkipForDialect(value = CockroachDB192Dialect.class, comment = "CockroachDB uses SERIALIZABLE isolation, and does not support this") + @SkipForDialect(value = SybaseASE15Dialect.class, comment = "Sybase seems to block on acquiring a SHARE lock when a different TX upgraded a SHARE to EXCLUSIVE lock, maybe the upgrade caused a table lock?") public void testDelete() throws InterruptedException { bookId = 1L; @@ -140,6 +143,7 @@ public void testDeleteNativeQuery() throws InterruptedException { @Test @SkipForDialect(value = CockroachDB192Dialect.class, comment = "CockroachDB uses SERIALIZABLE isolation, and does not support this") + @SkipForDialect(value = SybaseASE15Dialect.class, comment = "Sybase seems to block on acquiring a SHARE lock when a different TX upgraded a SHARE to EXCLUSIVE lock, maybe the upgrade caused a table lock?") public void testUpdate() throws InterruptedException { bookId = 4L; 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 edc30e455e9f..409feb979df0 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 @@ -65,7 +65,7 @@ protected Collection createCollection(PersistentClass persistentClass) { String expectMessage = "Association [abc] for entity [CollectionBinderTest] references unmapped class [List]"; try { - collectionBinder.bindOneToManySecondPass(collection, new HashMap(), null, collectionType, false, false, buildingContext, null); + collectionBinder.bindOneToManySecondPass(collection, new HashMap(), null, collectionType, false, null, buildingContext, null); } catch (MappingException e) { assertEquals(expectMessage, e.getMessage()); } diff --git a/hibernate-core/src/test/java/org/hibernate/connection/ConnectionCreatorTest.java b/hibernate-core/src/test/java/org/hibernate/connection/ConnectionCreatorTest.java index 953b1bc2582d..59af287e7552 100644 --- a/hibernate-core/src/test/java/org/hibernate/connection/ConnectionCreatorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/connection/ConnectionCreatorTest.java @@ -56,6 +56,7 @@ public R getService(Class serviceRole) { "jdbc:h2:mem:test-bad-urls;nosuchparam=saywhat", new Properties(), false, + null, null ); diff --git a/hibernate-core/src/test/java/org/hibernate/dialect/DialectContextTest.java b/hibernate-core/src/test/java/org/hibernate/dialect/DialectContextTest.java new file mode 100644 index 000000000000..9c2dd06df6b2 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/dialect/DialectContextTest.java @@ -0,0 +1,21 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.dialect; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hibernate.testing.orm.junit.DialectContext; +import org.junit.Test; + +public class DialectContextTest { + + @Test + public void smoke() { + Dialect current = DialectContext.getDialect(); + assertThat( current ).isNotNull(); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/id/IdentityIdEntityTest.java b/hibernate-core/src/test/java/org/hibernate/id/IdentityIdEntityTest.java new file mode 100644 index 000000000000..56a6aa824676 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/id/IdentityIdEntityTest.java @@ -0,0 +1,122 @@ +/* + * 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.id; + +import java.util.Date; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +import org.hibernate.SessionFactory; +import org.hibernate.boot.Metadata; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.H2Dialect; +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseUnitTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.fail; + + +/** + * @author Jan Schatteman + */ +@TestForIssue(jiraKey = "HHH-15561") +@RequiresDialect( value = { H2Dialect.class } ) +public class IdentityIdEntityTest extends BaseUnitTestCase { + + @Test + public void testIdentityEntityWithDisabledGetGeneratedKeys() { + StandardServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder() + .applySetting( AvailableSettings.HBM2DDL_AUTO, "create-drop" ) + .applySetting( AvailableSettings.USE_GET_GENERATED_KEYS, "false" ) + .build(); + + Metadata metadata = new MetadataSources( serviceRegistry ) + .addAnnotatedClass( IdentityEntity.class ) + .buildMetadata(); + + SessionFactory sessionFactory = metadata.getSessionFactoryBuilder() + .build(); + + doInHibernate( + () -> sessionFactory, + session -> { + try { + IdentityEntity ie = new IdentityEntity(); + ie.setTimestamp( new Date() ); + session.persist( ie ); + } + catch (Exception e) { + fail( "Creation of an IDENTITY-id-based entity failed when \"hibernate.jdbc.use_get_generated_keys\" was set to false (" + e.getMessage() + ")" ); + } + } + ); + } + + @Test + public void testIdentityEntityWithDisabledJdbcMetadataDefaults() { + StandardServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder() + .applySetting( AvailableSettings.HBM2DDL_AUTO, "create-drop" ) + .applySetting( "use_jdbc_metadata_defaults", "false" ) + .build(); + + Metadata metadata = new MetadataSources( serviceRegistry ) + .addAnnotatedClass( IdentityEntity.class ) + .buildMetadata(); + + SessionFactory sessionFactory = metadata.getSessionFactoryBuilder() + .build(); + + doInHibernate( + () -> sessionFactory, + session -> { + try { + IdentityEntity ie = new IdentityEntity(); + ie.setTimestamp( new Date() ); + session.persist( ie ); + } + catch (Exception e) { + fail( "Creation of an IDENTITY-id-based entity failed when \"use_jdbc_metadata_defaults\" was set to false (" + e.getMessage() + ")" ); + } + } + ); + } + + @Entity(name = "id_entity") + public static class IdentityEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private int id; + + private Date timestamp; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public Date getTimestamp() { + return timestamp; + } + + public void setTimestamp(Date timestamp) { + this.timestamp = timestamp; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/id/enhanced/OptimizerUnitTest.java b/hibernate-core/src/test/java/org/hibernate/id/enhanced/OptimizerUnitTest.java index 05579cbd995e..6e9dada1dae3 100644 --- a/hibernate-core/src/test/java/org/hibernate/id/enhanced/OptimizerUnitTest.java +++ b/hibernate-core/src/test/java/org/hibernate/id/enhanced/OptimizerUnitTest.java @@ -226,10 +226,15 @@ public void testRecoveredPooledOptimizerUsage() { Long next = ( Long ) optimizer.generate( sequence ); assertEquals( 1, next.intValue() ); + assertEquals( 1, sequence.getTimesCalled() ); + assertEquals( 1, sequence.getCurrentValue() ); + + next = ( Long ) optimizer.generate( sequence ); + assertEquals( 2, next.intValue() ); assertEquals( 2, sequence.getTimesCalled() ); assertEquals( 4, sequence.getCurrentValue() ); - // app ends, and starts back up (we should "lose" only 2 and 3 as id values) + // app ends, and starts back up (we should "lose" only 3 and 4 as id values) final Optimizer optimizer2 = buildPooledOptimizer( 1, 3 ); next = ( Long ) optimizer2.generate( sequence ); assertEquals( 5, next.intValue() ); 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 index fdc1d4a1e974..77302e0e00a2 100644 --- 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 @@ -17,6 +17,7 @@ import org.hibernate.dialect.CockroachDB192Dialect; import org.hibernate.dialect.PostgreSQL81Dialect; import org.hibernate.dialect.SQLServerDialect; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.testing.SkipForDialect; @@ -67,7 +68,8 @@ public void literalProjectionTest() throws Exception { value = { @SkipForDialect(value = SQLServerDialect.class, comment = "SQLServer does not support literals in group by statement"), @SkipForDialect(value = PostgreSQL81Dialect.class, comment = "PostgreSQL does not support literals in group by statement"), - @SkipForDialect( value = CockroachDB192Dialect.class, comment = "CockroachDB does not support literals in group by statement") + @SkipForDialect( value = CockroachDB192Dialect.class, comment = "CockroachDB does not support literals in group by statement"), + @SkipForDialect( value = SybaseASE15Dialect.class, comment = "Sybase does not support literals in group by statement") } ) public void testLiteralProjectionAndGroupBy() throws Exception { diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/query/NonWhereCriteriaTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/query/NonWhereCriteriaTest.java new file mode 100644 index 000000000000..2bf543d1e877 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/query/NonWhereCriteriaTest.java @@ -0,0 +1,81 @@ +/* + * 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.query; + +import java.util.List; +import javax.persistence.criteria.CriteriaUpdate; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.testing.TestForIssue; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +/** + * @author Jan Schatteman + */ +@TestForIssue( jiraKey = "HHH-15559" ) +public class NonWhereCriteriaTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Phone.class + }; + } + + @Before + public void prepareTestData() { + doInJPA( + this::entityManagerFactory, + entityManager -> { + Phone phone1 = new Phone(); + phone1.setSynced( true ); + Phone phone2 = new Phone(); + phone2.setSynced( true ); + entityManager.persist( phone1 ); + entityManager.persist( phone2 ); + } + ); + } + + @After + public void cleanupTestData() { + doInJPA( + this::entityManagerFactory, + entityManager -> { + entityManager.createQuery( "delete from Phone" ).executeUpdate(); + } + ); + } + + @Test + public void testNonWhereCriteriaUpdate() { + doInJPA( + this::entityManagerFactory, + (entityManager) -> { + CriteriaUpdate updateCriteria = entityManager.getCriteriaBuilder().createCriteriaUpdate( Phone.class ); + updateCriteria.from( Phone.class ); + updateCriteria.set( Phone_.isSynced, Boolean.FALSE ); + entityManager.createQuery( updateCriteria ).executeUpdate(); + } + ); + + doInJPA( + this::entityManagerFactory, + (entityManager) -> { + List results = entityManager.createQuery( "from Phone p where p.isSynced is false" ).getResultList(); + Assert.assertEquals( 2, results.size() ); + } + ); + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/query/Phone.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/query/Phone.java new file mode 100644 index 000000000000..ca7be0c09f62 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/query/Phone.java @@ -0,0 +1,39 @@ +/* + * 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.criteria.query; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +/** + * @author Jan Schatteman + */ +@Entity(name = "Phone") +public class Phone { + @Id + @GeneratedValue + private Long id; + + private Boolean isSynced; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Boolean getSynced() { + return isSynced; + } + + public void setSynced(Boolean synced) { + isSynced = synced; + } +} 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 91830e8c8682..a2872dd6aa66 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 @@ -38,6 +38,7 @@ import org.hibernate.dialect.DB2Dialect; import org.hibernate.dialect.DerbyDialect; import org.hibernate.dialect.H2Dialect; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.testing.RequiresDialect; @@ -48,6 +49,7 @@ @TestForIssue( jiraKey = "HHH-9731" ) @SkipForDialect(value = DB2Dialect.class, comment = "We would need casts in the case clauses. See HHH-12822.") @SkipForDialect(value = DerbyDialect.class, comment = "Derby requires either casted parameters or literals in the result arms of CASE expressions") +@SkipForDialect(value = SybaseASE15Dialect.class, comment = "Sybase requires either casted parameters or literals in the result arms of CASE expressions") public class SelectCaseTest extends BaseEntityManagerFunctionalTestCase { @Override diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/EntityGraphAttributeResolutionTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/EntityGraphAttributeResolutionTest.java index 6e3d7fabee1b..d6536dae7708 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/EntityGraphAttributeResolutionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/EntityGraphAttributeResolutionTest.java @@ -109,7 +109,7 @@ public void fetchAssocWithAdhocFetchGraph() { attributeNodes = { @NamedAttributeNode("permissions") }) - @Table(name = "groups") // Name 'group' not accepted by H2 + @Table( name = "t_group") // Name 'group' not accepted by H2 public static class Group { public static final String ENTITY_GRAPH = "group-with-permissions"; 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 bd94f204dff3..9e23af765a04 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 @@ -34,7 +34,7 @@ /** * @author Andrea Boriero */ -@RequiresDialectFeature(DialectChecks.SupportsJdbcDriverProxying.class) +@RequiresDialectFeature({DialectChecks.SupportsJdbcDriverProxying.class, DialectChecks.SupportsLockTimeouts.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/procedure/DateTimeParameterTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/procedure/DateTimeParameterTest.java index 343719430568..6229c9e32d9c 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/procedure/DateTimeParameterTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/procedure/DateTimeParameterTest.java @@ -31,6 +31,7 @@ import javax.persistence.TemporalType; import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.DerbyDialect; import org.hibernate.dialect.DerbyTenSevenDialect; import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess; import org.hibernate.engine.jdbc.spi.JdbcServices; @@ -40,6 +41,8 @@ import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.hibernate.testing.junit4.BaseUnitTestCase; import org.junit.After; import org.junit.Before; @@ -50,15 +53,20 @@ /** * @author Steve Ebersole */ -public class DateTimeParameterTest extends BaseUnitTestCase { - HibernateEntityManagerFactory entityManagerFactory; +@RequiresDialect(DerbyDialect.class) +public class DateTimeParameterTest extends BaseCoreFunctionalTestCase { private static GregorianCalendar nowCal = new GregorianCalendar(); private static Date now = new Date( nowCal.getTime().getTime() ); + @Override + protected Class[] getAnnotatedClasses() { + return new Class[]{Message.class}; + } + @Test public void testBindingCalendarAsDate() { - EntityManager em = entityManagerFactory.createEntityManager(); + EntityManager em = sessionFactory().createEntityManager(); em.getTransaction().begin(); try { @@ -76,7 +84,7 @@ public void testBindingCalendarAsDate() { @Test public void testBindingCalendarAsTime() { - EntityManager em = entityManagerFactory.createEntityManager(); + EntityManager em = sessionFactory().createEntityManager(); em.getTransaction().begin(); try { @@ -94,50 +102,19 @@ public void testBindingCalendarAsTime() { @Before public void startUp() { - // create the EMF - entityManagerFactory = Bootstrap.getEntityManagerFactoryBuilder( - buildPersistenceUnitDescriptor(), - buildSettingsMap() - ).build().unwrap( HibernateEntityManagerFactory.class ); - // create the procedures - createTestData( entityManagerFactory ); - createProcedures( entityManagerFactory ); - } - - private PersistenceUnitDescriptor buildPersistenceUnitDescriptor() { - return new BaseEntityManagerFunctionalTestCase.TestingPersistenceUnitDescriptorImpl( getClass().getSimpleName() ); - } - - @SuppressWarnings("unchecked") - private Map buildSettingsMap() { - Map settings = new HashMap(); - - settings.put( AvailableSettings.LOADED_CLASSES, Collections.singletonList( Message.class ) ); - - settings.put( org.hibernate.cfg.AvailableSettings.DIALECT, DerbyTenSevenDialect.class.getName() ); - settings.put( org.hibernate.cfg.AvailableSettings.DRIVER, org.apache.derby.jdbc.EmbeddedDriver.class.getName() ); - settings.put( org.hibernate.cfg.AvailableSettings.URL, "jdbc:derby:memory:hibernate-orm-testing;create=true" ); - settings.put( org.hibernate.cfg.AvailableSettings.USER, "" ); - - settings.put( org.hibernate.cfg.AvailableSettings.HBM2DDL_AUTO, "create-drop" ); - return settings; + createTestData( sessionFactory() ); + createProcedures( sessionFactory() ); } @After public void tearDown() { - if ( entityManagerFactory == null ) { - return; - } - - deleteTestData( entityManagerFactory ); - dropProcedures( entityManagerFactory ); - entityManagerFactory.close(); + deleteTestData( sessionFactory() ); + dropProcedures( sessionFactory() ); } - private void createProcedures(HibernateEntityManagerFactory emf) { - final SessionFactoryImplementor sf = emf.unwrap( SessionFactoryImplementor.class ); + private void createProcedures(SessionFactoryImplementor sf) { final JdbcConnectionAccess connectionAccess = sf.getServiceRegistry().getService( JdbcServices.class ).getBootstrapJdbcConnectionAccess(); final Connection conn; try { @@ -248,7 +225,7 @@ public static void retrieveTimestamp(Timestamp in, Timestamp[] out ) throws SQLE out[0] = in; } - private void createTestData(HibernateEntityManagerFactory entityManagerFactory) { + private void createTestData(SessionFactoryImplementor entityManagerFactory) { EntityManager em = entityManagerFactory.createEntityManager(); em.getTransaction().begin(); em.persist( new Message( 1, "test", now, now, now ) ); @@ -256,7 +233,7 @@ private void createTestData(HibernateEntityManagerFactory entityManagerFactory) em.close(); } - private void deleteTestData(HibernateEntityManagerFactory entityManagerFactory) { + private void deleteTestData(SessionFactoryImplementor entityManagerFactory) { EntityManager em = entityManagerFactory.createEntityManager(); em.getTransaction().begin(); em.createQuery( "delete from Message" ).executeUpdate(); @@ -264,8 +241,7 @@ private void deleteTestData(HibernateEntityManagerFactory entityManagerFactory) em.close(); } - private void dropProcedures(HibernateEntityManagerFactory emf) { - final SessionFactoryImplementor sf = emf.unwrap( SessionFactoryImplementor.class ); + private void dropProcedures(SessionFactoryImplementor sf) { final JdbcConnectionAccess connectionAccess = sf.getServiceRegistry().getService( JdbcServices.class ).getBootstrapJdbcConnectionAccess(); final Connection conn; try { diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/procedure/JpaTckUsageTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/procedure/JpaTckUsageTest.java index 0aa68a3856a7..67f6db6e7a63 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/procedure/JpaTckUsageTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/procedure/JpaTckUsageTest.java @@ -20,6 +20,7 @@ import javax.persistence.StoredProcedureQuery; import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.DerbyDialect; import org.hibernate.dialect.DerbyTenSevenDialect; import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess; import org.hibernate.engine.jdbc.spi.JdbcServices; @@ -30,6 +31,7 @@ import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.testing.FailureExpected; +import org.hibernate.testing.RequiresDialect; import org.hibernate.testing.junit4.BaseUnitTestCase; import org.junit.After; import org.junit.Before; @@ -46,11 +48,17 @@ * * @author Steve Ebersole */ -public class JpaTckUsageTest extends BaseUnitTestCase { +@RequiresDialect(DerbyDialect.class) +public class JpaTckUsageTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[]{User.class}; + } @Test public void testMultipleGetUpdateCountCalls() { - EntityManager em = entityManagerFactory.createEntityManager(); + EntityManager em = entityManagerFactory().createEntityManager(); em.getTransaction().begin(); try { @@ -68,7 +76,7 @@ public void testMultipleGetUpdateCountCalls() { @Test public void testBasicScalarResults() { - EntityManager em = entityManagerFactory.createEntityManager(); + EntityManager em = entityManagerFactory().createEntityManager(); em.getTransaction().begin(); try { @@ -95,7 +103,7 @@ public void testBasicScalarResults() { @Test @FailureExpected( jiraKey = "HHH-8416", message = "JPA TCK challenge" ) public void testHasMoreResultsHandlingTckChallenge() { - EntityManager em = entityManagerFactory.createEntityManager(); + EntityManager em = entityManagerFactory().createEntityManager(); em.getTransaction().begin(); try { @@ -113,7 +121,7 @@ public void testHasMoreResultsHandlingTckChallenge() { @Test public void testHasMoreResultsHandling() { - EntityManager em = entityManagerFactory.createEntityManager(); + EntityManager em = entityManagerFactory().createEntityManager(); em.getTransaction().begin(); try { @@ -130,7 +138,7 @@ public void testHasMoreResultsHandling() { @Test public void testResultClassHandling() { - EntityManager em = entityManagerFactory.createEntityManager(); + EntityManager em = entityManagerFactory().createEntityManager(); em.getTransaction().begin(); try { @@ -157,7 +165,7 @@ public void testResultClassHandling() { @Test public void testSettingInParamDefinedOnNamedStoredProcedureQuery() { - EntityManager em = entityManagerFactory.createEntityManager(); + EntityManager em = entityManagerFactory().createEntityManager(); em.getTransaction().begin(); try { StoredProcedureQuery query = em.createNamedStoredProcedureQuery( "positional-param" ); @@ -171,7 +179,7 @@ public void testSettingInParamDefinedOnNamedStoredProcedureQuery() { @Test public void testSettingNonExistingParams() { - EntityManager em = entityManagerFactory.createEntityManager(); + EntityManager em = entityManagerFactory().createEntityManager(); em.getTransaction().begin(); try { @@ -204,7 +212,7 @@ public void testSettingNonExistingParams() { @Test @FailureExpected( jiraKey = "HHH-8395", message = "Out of the frying pan into the fire: https://issues.apache.org/jira/browse/DERBY-211" ) public void testExecuteUpdate() { - EntityManager em = entityManagerFactory.createEntityManager(); + EntityManager em = entityManagerFactory().createEntityManager(); em.getTransaction().begin(); try { @@ -248,57 +256,21 @@ public void testParameterRegistration() { // "$$"; // public static final String deleteAllUsers_DROP_CMD = "DROP ALIAS deleteAllUsers IF EXISTS"; - HibernateEntityManagerFactory entityManagerFactory; - @Before public void startUp() { - // create the EMF - entityManagerFactory = Bootstrap.getEntityManagerFactoryBuilder( - buildPersistenceUnitDescriptor(), - buildSettingsMap() - ).build().unwrap( HibernateEntityManagerFactory.class ); - // create the procedures - createTestUser( entityManagerFactory ); - createProcedures( entityManagerFactory ); - } - - private PersistenceUnitDescriptor buildPersistenceUnitDescriptor() { - return new BaseEntityManagerFunctionalTestCase.TestingPersistenceUnitDescriptorImpl( getClass().getSimpleName() ); - } - - @SuppressWarnings("unchecked") - private Map buildSettingsMap() { - Map settings = new HashMap(); - - settings.put( AvailableSettings.LOADED_CLASSES, Collections.singletonList( User.class ) ); - - settings.put( org.hibernate.cfg.AvailableSettings.DIALECT, DerbyTenSevenDialect.class ); - settings.put( org.hibernate.cfg.AvailableSettings.DRIVER, org.apache.derby.jdbc.EmbeddedDriver.class.getName() ); -// settings.put( org.hibernate.cfg.AvailableSettings.URL, "jdbc:derby:/tmp/hibernate-orm-testing;create=true" ); - settings.put( org.hibernate.cfg.AvailableSettings.URL, "jdbc:derby:memory:hibernate-orm-testing;create=true" ); - settings.put( org.hibernate.cfg.AvailableSettings.USER, "" ); - - settings.put( org.hibernate.cfg.AvailableSettings.HBM2DDL_AUTO, "create-drop" ); - settings.put( org.hibernate.cfg.AvailableSettings.USE_NEW_ID_GENERATOR_MAPPINGS, "true" ); - settings.put( org.hibernate.cfg.AvailableSettings.DIALECT, DerbyTenSevenDialect.class.getName() ); - return settings; + createTestUser( entityManagerFactory() ); + createProcedures( entityManagerFactory() ); } @After public void tearDown() { - if ( entityManagerFactory == null ) { - return; - } - - deleteTestUser( entityManagerFactory ); - dropProcedures( entityManagerFactory ); - entityManagerFactory.close(); + deleteTestUser( entityManagerFactory() ); + dropProcedures( entityManagerFactory() ); } - private void createProcedures(HibernateEntityManagerFactory emf) { - final SessionFactoryImplementor sf = emf.unwrap( SessionFactoryImplementor.class ); + private void createProcedures(SessionFactoryImplementor sf) { final JdbcConnectionAccess connectionAccess = sf.getServiceRegistry().getService( JdbcServices.class ).getBootstrapJdbcConnectionAccess(); final Connection conn; try { @@ -395,7 +367,7 @@ public static void deleteAllUsers() throws SQLException { conn.close(); } - private void createTestUser(HibernateEntityManagerFactory entityManagerFactory) { + private void createTestUser(SessionFactoryImplementor entityManagerFactory) { EntityManager em = entityManagerFactory.createEntityManager(); em.getTransaction().begin(); @@ -404,7 +376,7 @@ private void createTestUser(HibernateEntityManagerFactory entityManagerFactory) em.close(); } - private void deleteTestUser(HibernateEntityManagerFactory entityManagerFactory) { + private void deleteTestUser(SessionFactoryImplementor entityManagerFactory) { EntityManager em = entityManagerFactory.createEntityManager(); em.getTransaction().begin(); em.createQuery( "delete from User" ).executeUpdate(); @@ -412,8 +384,7 @@ private void deleteTestUser(HibernateEntityManagerFactory entityManagerFactory) em.close(); } - private void dropProcedures(HibernateEntityManagerFactory emf) { - final SessionFactoryImplementor sf = emf.unwrap( SessionFactoryImplementor.class ); + private void dropProcedures(SessionFactoryImplementor sf) { final JdbcConnectionAccess connectionAccess = sf.getServiceRegistry().getService( JdbcServices.class ).getBootstrapJdbcConnectionAccess(); final Connection conn; try { diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/query/CriteriaUpdateWithParametersTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/CriteriaUpdateWithParametersTest.java new file mode 100644 index 000000000000..10313f7d6439 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/CriteriaUpdateWithParametersTest.java @@ -0,0 +1,111 @@ +package org.hibernate.jpa.test.query; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Query; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaUpdate; +import javax.persistence.criteria.ParameterExpression; +import javax.persistence.criteria.Root; +import javax.persistence.metamodel.EntityType; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +@TestForIssue(jiraKey = "HHH-15113") +public class CriteriaUpdateWithParametersTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Person.class + }; + } + + @Test + public void testCriteriaUpdate() { + doInJPA( + this::entityManagerFactory, + entityManager -> { + final CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); + final CriteriaUpdate criteriaUpdate = criteriaBuilder.createCriteriaUpdate( Person.class ); + final Root root = criteriaUpdate.from( Person.class ); + + final ParameterExpression intValueParameter = criteriaBuilder.parameter( Integer.class ); + final ParameterExpression stringValueParameter = criteriaBuilder.parameter( String.class ); + + final EntityType personEntityType = entityManager.getMetamodel().entity( Person.class ); + + criteriaUpdate.set( + root.get( personEntityType.getSingularAttribute( "age", Integer.class ) ), + intValueParameter + ); + + criteriaUpdate.where( criteriaBuilder.equal( + root.get( personEntityType.getSingularAttribute( "name", String.class ) ), + stringValueParameter + ) ); + + final Query query = entityManager.createQuery( criteriaUpdate ); + query.setParameter( intValueParameter, 9 ); + query.setParameter( stringValueParameter, "Luigi" ); + + query.executeUpdate(); + } + ); + } + + @Test + public void testCriteriaUpdate2() { + doInJPA( + this::entityManagerFactory, + entityManager -> { + final CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); + final CriteriaUpdate criteriaUpdate = criteriaBuilder.createCriteriaUpdate( Person.class ); + final Root root = criteriaUpdate.from( Person.class ); + + final ParameterExpression intValueParameter = criteriaBuilder.parameter( Integer.class ); + final ParameterExpression stringValueParameter = criteriaBuilder.parameter( String.class ); + + criteriaUpdate.set( "age", intValueParameter ); + criteriaUpdate.where( criteriaBuilder.equal( root.get( "name" ), stringValueParameter ) ); + + final Query query = entityManager.createQuery( criteriaUpdate ); + query.setParameter( intValueParameter, 9 ); + query.setParameter( stringValueParameter, "Luigi" ); + + query.executeUpdate(); + } + ); + } + + @Entity(name = "Person") + public static class Person { + + @Id + private String id; + + private String name; + + private Integer age; + + public Person() { + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public Integer getAge() { + return age; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/query/NonWhereQueryTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/NonWhereQueryTest.java new file mode 100644 index 000000000000..e44ab67d1e7b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/NonWhereQueryTest.java @@ -0,0 +1,143 @@ +/* + * 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 java.util.Objects; +import java.util.UUID; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.TestForIssue; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +/** + * @author Jan Schatteman + */ +@TestForIssue( jiraKey = "HHH-15257" ) +public class NonWhereQueryTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + NonWhereQueryTest.TestUser.class + }; + } + + @Before + public void prepareTestData() { + doInJPA( + this::entityManagerFactory, + entityManager -> { + TestUser user = new TestUser(); + user.setLoggedIn( true ); + entityManager.persist( user ); + } + ); + } + + @After + public void cleanupTestData() { + doInJPA( + this::entityManagerFactory, + entityManager -> { + entityManager.createQuery("delete from TestUser").executeUpdate(); + } + ); + } + + @Test + public void testNonWhereQueryOnJoinInheritedTable() { + doInJPA( + this::entityManagerFactory, + entityManager -> { + int i = entityManager.createQuery( "update TestUser x set x.loggedIn = false" ).executeUpdate(); + Assert.assertEquals(1, i); + } + ); + } + + @Entity(name = "TestUser") + public static class TestUser extends AbstractEntity { + + @Column + private Boolean loggedIn; + + public TestUser() { + super(); + } + + public boolean isLoggedIn() { + if (this.loggedIn == null) { + return false; + } + return this.loggedIn; + } + + public void setLoggedIn(boolean loggedIn) { + this.loggedIn = loggedIn; + } + } + + @Entity(name = "AbstractEntity") + @Inheritance(strategy = InheritanceType.JOINED) + public static abstract class AbstractEntity implements Comparable { + + private final UUID uuid; + + @Id + @GeneratedValue + private int id; + + public int getId() { + return this.id; + } + + public UUID getUuid() { + return this.uuid; + } + + public AbstractEntity() { + super(); + this.uuid = UUID.randomUUID(); + } + + @Override + public boolean equals(Object obj) { + int usedId = this.getId(); + if (usedId > 0) { + return (obj instanceof AbstractEntity) && (usedId == ((AbstractEntity) obj).getId()); + } + return super.equals(obj); + } + + @Override + public int compareTo(AbstractEntity o) { + return Integer.compare(this.getId(), o.getId()); + } + + @Override + public int hashCode() { + final int usedId = this.getId(); + if (usedId > 0) { + return Objects.hash( this.getClass().toString(), usedId); + } + return super.hashCode(); + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/query/NotFoundAssociationQueryTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/NotFoundAssociationQueryTest.java new file mode 100644 index 000000000000..2f0adc99d07b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/NotFoundAssociationQueryTest.java @@ -0,0 +1,179 @@ +package org.hibernate.jpa.test.query; + +import java.util.List; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToOne; +import javax.persistence.TypedQuery; + +import org.hibernate.annotations.NotFound; +import org.hibernate.annotations.NotFoundAction; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +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; + +public class NotFoundAssociationQueryTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Parent.class, Child.class }; + } + + @Before + public void setUp() { + doInJPA( this::entityManagerFactory, entityManager -> { + Parent parent1 = new Parent( 1, "usr1", null ); + + Child child = new Child( 3, "Fab" ); + Parent parent2 = new Parent( 2, "usr2", child ); + + entityManager.persist( child ); + entityManager.persist( parent1 ); + entityManager.persist( parent2 ); + } ); + } + + @After + public void tearDown() { + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.createQuery( "delete from Parent" ).executeUpdate(); + } + ); + } + + @Test + public void testIt() { + doInJPA( this::entityManagerFactory, entityManager -> { + + TypedQuery query = entityManager.createQuery( + "select parent " + + "from Parent parent " + + "where exists (select 1 " + + "from Child child " + + "where child.parent.name = parent.name and child.parent.id = :id)", + Parent.class + ).setParameter( "id", 2 ); + + List entityList = query.getResultList(); + assertThat( entityList.size(), is( 1 ) ); + assertThat( entityList.get( 0 ).name, is( "usr2" ) ); + + } ); + } + + @Test + public void testIt2() { + doInJPA( this::entityManagerFactory, entityManager -> { + + TypedQuery query = entityManager.createQuery( + "select parent " + + "from Parent parent " + + "where exists (select 1 " + + "from Child child " + + "where child.name = parent.child.name )", + Parent.class + ); + + List entityList = query.getResultList(); + assertThat( entityList.size(), is( 1 ) ); + assertThat( entityList.get( 0 ).name, is( "usr2" ) ); + + } ); + } + + @Test + public void testIt3() { + doInJPA( this::entityManagerFactory, entityManager -> { + + TypedQuery query = entityManager.createQuery( + "select parent " + + "from Parent parent " + + "where parent.child.id = (select child.id " + + "from Child child " + + "where parent.child.id = :id)", + Parent.class + ).setParameter( "id", 3 ); + + List entityList = query.getResultList(); + assertThat( entityList.size(), is( 1 ) ); + assertThat( entityList.get( 0 ).name, is( "usr2" ) ); + + } ); + } + + @Test + public void testIt4() { + doInJPA( this::entityManagerFactory, entityManager -> { + + TypedQuery query = entityManager.createQuery( + "select parent " + + "from Parent parent " + + "where exists (select 1 " + + "from Child child " + + "where parent.child.id = :id)", + Parent.class + ).setParameter( "id", 3 ); + + List entityList = query.getResultList(); + assertThat( entityList.size(), is( 1 ) ); + assertThat( entityList.get( 0 ).name, is( "usr2" ) ); + + } ); + } + + @Entity(name = "Parent") + public static class Parent { + + @Id + private Integer id; + + @ManyToOne + @JoinColumn(name = "source_fk", referencedColumnName = "id") + @NotFound(action = NotFoundAction.IGNORE) + private Child child; + + private String name; + + Parent() { + } + + public Parent(Integer id, String name, Child child) { + this.id = id; + this.name = name; + this.child = child; + } + + public Integer getId() { + return id; + } + } + + @Entity(name = "Child") + public static class Child { + @Id + private Integer id; + + @OneToOne(mappedBy = "child") + @NotFound(action = NotFoundAction.IGNORE) + private Parent parent; + + private String name; + + public Child() { + } + + public Child(Integer id, String name) { + this.id = id; + this.name = name; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/query/QueryTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/QueryTest.java index dc0090ac425f..77831491b4e5 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/query/QueryTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/QueryTest.java @@ -30,7 +30,6 @@ import org.hibernate.dialect.Oracle8iDialect; import org.hibernate.dialect.PostgreSQL9Dialect; import org.hibernate.dialect.PostgresPlusDialect; -import org.hibernate.dialect.SybaseDialect; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.jpa.test.Distributor; @@ -136,7 +135,6 @@ public void testPagedQuery() throws Exception { } @Test - @SkipForDialect(value = SybaseDialect.class, comment = "Null == null on Sybase") @SkipForDialect(value = DerbyDialect.class, comment = "Cannot convert untyped null (assumed to be VARBINARY type) to VARCHAR") public void testNullPositionalParameter() throws Exception { EntityManager em = getOrCreateEntityManager(); @@ -167,7 +165,6 @@ public void testNullPositionalParameter() throws Exception { } @Test - @SkipForDialect(value = SybaseDialect.class, comment = "Null == null on Sybase") @SkipForDialect(value = DerbyDialect.class, comment = "Cannot convert untyped null (assumed to be VARBINARY type) to VARCHAR") public void testNullPositionalParameterParameter() throws Exception { EntityManager em = getOrCreateEntityManager(); @@ -215,7 +212,6 @@ public Class getParameterType() { } @Test - @SkipForDialect(value = SybaseDialect.class, comment = "Null == null on Sybase") @SkipForDialect(value = DerbyDialect.class, comment = "Cannot convert untyped null (assumed to be VARBINARY type) to VARCHAR") public void testNullPositionalParameterParameterIncompatible() throws Exception { EntityManager em = getOrCreateEntityManager(); @@ -263,7 +259,6 @@ public Class getParameterType() { } @Test - @SkipForDialect(value = SybaseDialect.class, comment = "Null == null on Sybase") @SkipForDialect(value = DerbyDialect.class, comment = "Cannot convert untyped null (assumed to be VARBINARY type) to VARCHAR") public void testNullNamedParameter() throws Exception { EntityManager em = getOrCreateEntityManager(); @@ -294,7 +289,6 @@ public void testNullNamedParameter() throws Exception { } @Test - @SkipForDialect(value = SybaseDialect.class, comment = "Null == null on Sybase") @SkipForDialect(value = DerbyDialect.class, comment = "Cannot convert untyped null (assumed to be VARBINARY type) to VARCHAR") public void testNullNamedParameterParameter() throws Exception { EntityManager em = getOrCreateEntityManager(); @@ -341,7 +335,6 @@ public Class getParameterType() { } @Test - @SkipForDialect(value = SybaseDialect.class, comment = "Null == null on Sybase") @SkipForDialect(value = DerbyDialect.class, comment = "Cannot convert untyped null (assumed to be VARBINARY type) to VARCHAR") public void testNullNamedParameterParameterIncompatible() throws Exception { EntityManager em = getOrCreateEntityManager(); @@ -392,7 +385,6 @@ public Class getParameterType() { @SkipForDialect(value = PostgreSQL9Dialect.class, jiraKey = "HHH-10312", comment = "Cannot convert untyped null (assumed to be bytea type) to bigint") @SkipForDialect(value = PostgresPlusDialect.class, jiraKey = "HHH-10312", comment = "Cannot convert untyped null (assumed to be bytea type) to bigint") @SkipForDialect(value = CockroachDB192Dialect.class, jiraKey = "HHH-10312", comment = "Cannot convert untyped null (assumed to be bytea type) to bigint") - @SkipForDialect(value = SybaseDialect.class, comment = "Null == null on Sybase") @SkipForDialect(value = DerbyDialect.class, comment = "Cannot convert untyped null (assumed to be VARBINARY type) to INTEGER") public void testNativeQueryNullPositionalParameter() throws Exception { EntityManager em = getOrCreateEntityManager(); @@ -430,7 +422,6 @@ public void testNativeQueryNullPositionalParameter() throws Exception { @SkipForDialect(value = PostgresPlusDialect.class, jiraKey = "HHH-10312", comment = "Cannot convert untyped null (assumed to be bytea type) to bigint") @SkipForDialect(value = CockroachDB192Dialect.class, jiraKey = "HHH-10312", comment = "Cannot convert untyped null (assumed to be bytea type) to bigint") @SkipForDialect(value = Oracle8iDialect.class, comment = "ORA-00932: inconsistent datatypes: expected NUMBER got BINARY") - @SkipForDialect(value = SybaseDialect.class, comment = "Null == null on Sybase") @SkipForDialect(value = DerbyDialect.class, comment = "Cannot convert untyped null (assumed to be VARBINARY type) to INTEGER") public void testNativeQueryNullPositionalParameterParameter() throws Exception { EntityManager em = getOrCreateEntityManager(); @@ -484,7 +475,6 @@ public Class getParameterType() { @SkipForDialect(value = PostgreSQL9Dialect.class, jiraKey = "HHH-10312", comment = "Cannot convert untyped null (assumed to be bytea type) to bigint") @SkipForDialect(value = PostgresPlusDialect.class, jiraKey = "HHH-10312", comment = "Cannot convert untyped null (assumed to be bytea type) to bigint") @SkipForDialect(value = CockroachDB192Dialect.class, jiraKey = "HHH-10312", comment = "Cannot convert untyped null (assumed to be bytea type) to bigint") - @SkipForDialect(value = SybaseDialect.class, comment = "Null == null on Sybase") @SkipForDialect(value = DerbyDialect.class, comment = "Cannot convert untyped null (assumed to be VARBINARY type) to INTEGER") public void testNativeQueryNullNamedParameter() throws Exception { EntityManager em = getOrCreateEntityManager(); @@ -522,7 +512,6 @@ public void testNativeQueryNullNamedParameter() throws Exception { @SkipForDialect(value = PostgresPlusDialect.class, jiraKey = "HHH-10312", comment = "Cannot convert untyped null (assumed to be bytea type) to bigint") @SkipForDialect(value = CockroachDB192Dialect.class, jiraKey = "HHH-10312", comment = "Cannot convert untyped null (assumed to be bytea type) to bigint") @SkipForDialect(value = Oracle8iDialect.class, comment = "ORA-00932: inconsistent datatypes: expected NUMBER got BINARY") - @SkipForDialect(value = SybaseDialect.class, comment = "Null == null on Sybase") @SkipForDialect(value = DerbyDialect.class, comment = "Cannot convert untyped null (assumed to be VARBINARY type) to INTEGER") public void testNativeQueryNullNamedParameterParameter() throws Exception { EntityManager em = getOrCreateEntityManager(); diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/query/ReuseCriteriaWithMixedParametersTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/ReuseCriteriaWithMixedParametersTest.java new file mode 100644 index 000000000000..43c95fc0fac5 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/ReuseCriteriaWithMixedParametersTest.java @@ -0,0 +1,222 @@ +/* + * 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 java.time.Instant; +import java.util.Date; +import java.util.List; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Query; +import javax.persistence.TypedQuery; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.ParameterExpression; +import javax.persistence.criteria.Root; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.jpa.test.Wallet; +import org.hibernate.jpa.test.Wallet_; + +import org.hibernate.testing.TestForIssue; +import org.junit.After; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; + +@TestForIssue(jiraKey = "HHH-15142") +public class ReuseCriteriaWithMixedParametersTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Wallet.class, + Person.class + }; + } + + @After + public void tearDown() { + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.createQuery( "delete from Person" ).executeUpdate(); + } + ); + } + + @Test + public void cqReuse() { + doInJPA( this::entityManagerFactory, entityManager -> { + final CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); + final CriteriaQuery criteriaQuery = criteriaBuilder.createQuery( Wallet.class ); + final Root root = criteriaQuery.from( Wallet.class ); + + final ParameterExpression stringValueParameter = criteriaBuilder.parameter( String.class ); + + criteriaQuery.where( + criteriaBuilder.like( + root.get( Wallet_.model ), + stringValueParameter + ), + criteriaBuilder.lessThan( + root.get( Wallet_.marketEntrance ), + criteriaBuilder.literal( Date.from( Instant.EPOCH ) ) + ) + ); + + Query query = entityManager.createQuery( criteriaQuery ); + query.setParameter( stringValueParameter, "Z%" ); + + query.getResultList(); + + query = entityManager.createQuery( criteriaQuery ); + query.setParameter( stringValueParameter, "A%" ); + + query.getResultList(); + + } ); + } + + @Test + public void likeCqReuse() { + doInJPA( this::entityManagerFactory, entityManager -> { + final CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); + final CriteriaQuery criteriaQuery = criteriaBuilder.createQuery( Wallet.class ); + final Root root = criteriaQuery.from( Wallet.class ); + + final ParameterExpression stringValueParameter = criteriaBuilder.parameter( String.class ); + + criteriaQuery.where( + criteriaBuilder.like( + root.get( Wallet_.model ), + stringValueParameter, + '/' + ) + ); + + Query query = entityManager.createQuery( criteriaQuery ); + query.setParameter( stringValueParameter, "Z%" ); + + query.getResultList(); + + query = entityManager.createQuery( criteriaQuery ); + query.setParameter( stringValueParameter, "A%" ); + + query.getResultList(); + + } ); + } + + @Test + public void predicateReuse() { + doInJPA( this::entityManagerFactory, entityManager -> { + final CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); + final CriteriaQuery criteriaQuery = criteriaBuilder.createQuery( Wallet.class ); + final Root root = criteriaQuery.from( Wallet.class ); + + final ParameterExpression stringValueParameter = criteriaBuilder.parameter( String.class ); + final ParameterExpression dateValueParameter = criteriaBuilder.parameter( Date.class ); + + criteriaQuery.where( + criteriaBuilder.like( + root.get( Wallet_.model ), + stringValueParameter + ) + ); + + Query query = entityManager.createQuery( criteriaQuery ); + query.setParameter( stringValueParameter, "Z%" ); + + query.getResultList(); + + criteriaQuery.where( + criteriaBuilder.like( + root.get( Wallet_.model ), + stringValueParameter + ), + criteriaBuilder.lessThan( + root.get( Wallet_.marketEntrance ), + dateValueParameter + ) + ); + + query = entityManager.createQuery( criteriaQuery ); + query.setParameter( stringValueParameter, "A%" ); + query.setParameter( dateValueParameter, Date.from( Instant.EPOCH ) ); + + query.getResultList(); + } ); + } + + @Test + public void testLikePredicate() { + doInJPA( this::entityManagerFactory, entityManager -> { + + entityManager.persist( new Person( "Person 1" ) ); + entityManager.persist( new Person( "Person 2" ) ); + } + ); + + doInJPA( this::entityManagerFactory, entityManager -> { + final CriteriaBuilder cb = entityManager.getCriteriaBuilder(); + final CriteriaQuery personQuery = cb.createQuery( Person.class ); + final Root root = personQuery.from( Person.class ); + final ParameterExpression pattern = cb.parameter( String.class ); + CriteriaQuery criteriaQuery = personQuery + .where( cb.like( + root.get( "name" ), + pattern, + cb.literal( '\\' ) + ) ); + for ( int i = 0; i < 2; i++ ) { + + final TypedQuery query = entityManager.createQuery( criteriaQuery ); + query.setParameter( pattern, "%_1" ); + final List result = query.getResultList(); + + assertEquals( 1, result.size() ); + } + } + ); + + } + + @Entity(name = "Person") + public static class Person { + @Id + @GeneratedValue + private Long id; + + private String name; + + public Person() { + } + + public Person(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; + } + } + + +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/query/TupleQueryRetrievePrimitiveTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/TupleQueryRetrievePrimitiveTest.java new file mode 100644 index 000000000000..5765045efa4f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/TupleQueryRetrievePrimitiveTest.java @@ -0,0 +1,85 @@ +package org.hibernate.jpa.test.query; + +import org.hibernate.jpa.test.metamodel.AbstractMetamodelSpecificTest; +import org.hibernate.jpa.test.metamodel.ThingWithQuantity; +import org.hibernate.jpa.test.metamodel.ThingWithQuantity_; +import org.hibernate.testing.TestForIssue; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import javax.persistence.EntityManager; +import javax.persistence.Tuple; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Path; +import javax.persistence.criteria.Root; + +@TestForIssue( jiraKey = "HHH-15454" ) +public class TupleQueryRetrievePrimitiveTest extends AbstractMetamodelSpecificTest { + + public static final int QUANTITY_OF_THING = 3; + private EntityManager em; + + + @Before + public void createThingWithQuantity() { + em = getOrCreateEntityManager(); + em.getTransaction().begin(); + + ThingWithQuantity thing = new ThingWithQuantity(); + thing.setId( "thingWithQuantity3" ); + thing.setName( "3 Things" ); + thing.setQuantity(QUANTITY_OF_THING); + em.persist( thing ); + + em.getTransaction().commit(); + } + + @After + public void endEntityManager() { + em.close(); + } + + @Test + public void testRetrieveTupleEntryWithPrimitiveType() { + final Tuple result = queryTuple(); + final int quantity = result.get(ThingWithQuantity_.quantity.getName(), int.class); + Assert.assertEquals(QUANTITY_OF_THING, quantity); + } + + @Test + public void testRetrieveTupleEntryWithMetadata() { + final Tuple result = queryTuple(); + final int quantity = result.get(ThingWithQuantity_.quantity.getName(), ThingWithQuantity_.quantity.getJavaType()); + Assert.assertEquals(QUANTITY_OF_THING, quantity); + } + + @Test + public void testRetrieveTupleEntryFromIndex() { + final Tuple result = queryTuple(); + final int quantity = result.get(0, ThingWithQuantity_.quantity.getJavaType()); + Assert.assertEquals(QUANTITY_OF_THING, quantity); + } + + @Test + public void testRetrieveTupleEntryWithTupleElement() { + final CriteriaBuilder cb = em.getCriteriaBuilder(); + final CriteriaQuery query = cb.createTupleQuery(); + final Root thingWithQuantity = query.from(ThingWithQuantity.class); + final Path tupleElement = thingWithQuantity.get(ThingWithQuantity_.quantity); + query.multiselect(tupleElement.alias(ThingWithQuantity_.quantity.getName())); + Tuple result = em.createQuery(query).setMaxResults(1).getSingleResult(); + final int quantity = result.get(tupleElement); + Assert.assertEquals(QUANTITY_OF_THING, quantity); + } + + private Tuple queryTuple() { + final CriteriaBuilder cb = em.getCriteriaBuilder(); + final CriteriaQuery query = cb.createTupleQuery(); + final Root thingWithQuantity = query.from(ThingWithQuantity.class); + query.multiselect(thingWithQuantity.get(ThingWithQuantity_.quantity).alias(ThingWithQuantity_.quantity.getName())); + return em.createQuery(query).setMaxResults(1).getSingleResult(); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/query/TypedQueryResultListTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/TypedQueryResultListTest.java new file mode 100644 index 000000000000..8d5e7ed4f2ad --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/TypedQueryResultListTest.java @@ -0,0 +1,210 @@ +package org.hibernate.jpa.test.query; + +import java.util.List; +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.TypedQuery; + +import org.hibernate.annotations.NotFound; +import org.hibernate.annotations.NotFoundAction; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.FailureExpected; +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; + +public class TypedQueryResultListTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Parent.class }; + } + + @Before + public void createTestData() { + doInJPA( this::entityManagerFactory, entityManager -> { + Parent parent1 = new Parent( 1, "usr1", null ); + Parent parent2 = new Parent( 1, "usr2", null ); + entityManager.persist( parent1 ); + entityManager.persist( parent2 ); + } ); + } + + @After + public void dropTestData() { + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.createQuery( "delete Parent" ).executeUpdate(); + } ); + } + + + @Test + @FailureExpected( + jiraKey = "HHH-15060", + message = "This was the exact reported case. Even though @NotFound support is buggy, this " + + "query is not valid for the expected results. See `#badExpectationResultBaselineTest` " + + "for additional discussion about why this is an incorrect expectation." + ) + public void badExpectationResultTest() { + doInJPA( this::entityManagerFactory, entityManager -> { + TypedQuery query = entityManager.createQuery( + "select parent " + + "from Parent parent " + + "where exists ( " + + " select 1 " + + " from Parent otherParent " + + " where lower(otherParent.text) like :name" + + " and (parent.id = otherParent.sourceParent.id or parent.number = otherParent.number)" + + ")", + Parent.class + ); + + query.setParameter( "name", "usr1" ); + List entityList = query.getResultList(); + assertThat( entityList.size(), is( 2 ) ); + } ); + } + + /** + * Adjustment to {@link #badExpectationResultTest} in terms of the results which should actually be expected + * + * @see #actualExpectationResultBaselineTest + */ + @Test + public void actualExpectationResultTest() { + doInJPA( this::entityManagerFactory, entityManager -> { + TypedQuery query = entityManager.createQuery( + "select parent " + + "from Parent parent " + + "where exists ( " + + " select 1 " + + " from Parent otherParent " + + " where lower(otherParent.text) like :name" + + " and (parent.id = otherParent.sourceParent.id or parent.number = otherParent.number)" + + ")", + Parent.class + ); + + query.setParameter( "name", "usr1" ); + List entityList = query.getResultList(); + assertThat( entityList.size(), is( 0 ) ); + } ); + } + + /** + * A baseline test for {@link #badExpectationResultTest}, with the expectation adjustment described in + * {@link #actualExpectationResultTest()}. + * + * Here, instead of `.id` references (which are handled specially even outside of `@NotFound`), we use + * non-id references, which should ultimately return the same results. + */ + @Test + public void actualExpectationResultBaselineTest() { + doInJPA( this::entityManagerFactory, entityManager -> { + TypedQuery query = entityManager.createQuery( + "select parent " + + "from Parent parent " + + "where exists ( " + + " select 1 " + + " from Parent otherParent " + + " where lower(otherParent.text) like :name " + + " and (parent.text = otherParent.sourceParent.text or parent.number = otherParent.number) " + + ")", + Parent.class + ); + + query.setParameter( "name", "usr1" ); + List entityList = query.getResultList(); + assertThat( entityList.size(), is( 0 ) ); + } ); + } + + /** + * An adjusted query showing the results wanted in the original report. + * + * Actually a series of adjusted queries, showing a few possibilities + */ + @Test + public void expectedResultQueryAdjustmentTest() { + doInJPA( this::entityManagerFactory, entityManager -> { + TypedQuery query = entityManager.createQuery( + "select parent " + + "from Parent parent " + + "where exists ( " + + " select 1 " + + " from Parent otherParent " + + " where lower(otherParent.text) like :name" + + " and (parent = otherParent.sourceParent or parent.number = otherParent.number)" + + ")", + Parent.class + ); + + query.setParameter( "name", "usr1" ); + List entityList = query.getResultList(); + assertThat( entityList.size(), is( 2 ) ); + } ); + } + + @Test + public void testIt2() { + doInJPA( this::entityManagerFactory, entityManager -> { + TypedQuery query = entityManager.createQuery( + "select parent " + + "from Parent parent " + + "where exists (select 1 " + + "from Parent otherParent " + + "where lower(otherParent.text) like :name and (parent = otherParent.sourceParent or parent.number = otherParent.number))", + Parent.class + ); + + query.setParameter( "name", "usr1" ); + List entityList = query.getResultList(); + assertThat( entityList.size(), is( 2 ) ); + } ); + + + } + + @Entity(name = "Parent") + public static class Parent { + + @Id + @GeneratedValue + private int id; + + @Column(name = "num", nullable = false, precision = 9) + private Integer number; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "source_fk", referencedColumnName = "id") + @NotFound(action = NotFoundAction.IGNORE) + private Parent sourceParent; + + @Column(name = "txt", nullable = false, length = 20) + private String text; + + Parent() { + } + + public Parent(Integer num, String txt, Parent source) { + this.number = num; + this.text = txt; + this.sourceParent = source; + } + + public int getId() { + return id; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/batch/FailingAddToBatchTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/batch/FailingAddToBatchTest.java new file mode 100644 index 000000000000..068d9e280858 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/batch/FailingAddToBatchTest.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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.jpa.test.transaction.batch; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; +import static org.junit.jupiter.api.Assertions.fail; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.jdbc.batch.internal.BatchBuilderImpl; +import org.hibernate.engine.jdbc.batch.internal.BatchBuilderInitiator; +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.TestForIssue; +import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; +import org.hibernate.testing.orm.junit.Jpa; +import org.hibernate.testing.orm.junit.Setting; +import org.hibernate.testing.orm.junit.SettingProvider; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +@TestForIssue(jiraKey = "HHH-15082") +@Jpa( + annotatedClasses = { + FailingAddToBatchTest.MyEntity.class + }, + integrationSettings = { + @Setting(name = AvailableSettings.STATEMENT_BATCH_SIZE, value = "50") + }, + settingProviders = { + @SettingProvider( + settingName = BatchBuilderInitiator.BUILDER, + provider = FailingAddToBatchTest.BatchBuilderSettingProvider.class + ) + } +) +public class FailingAddToBatchTest { + + private static TestBatch testBatch; + + @BeforeEach + public void setup() { + TestBatch.nextAddToBatchFailure.set( null ); + } + + @Test + public void testInsert(EntityManagerFactoryScope scope) { + RuntimeException simulatedAddToBatchFailure = new RuntimeException( "Simulated RuntimeException" ); + + scope.inTransaction( em -> { + assertThatThrownBy( () -> { + MyEntity entity = new MyEntity(); + entity.setText( "initial" ); + TestBatch.nextAddToBatchFailure.set( simulatedAddToBatchFailure ); + em.persist( entity ); + em.flush(); + } ) + .isSameAs( simulatedAddToBatchFailure ); + + assertAllStatementsAreClosed( testBatch.createdStatements ); + } ); + } + + @Test + public void testUpdate(EntityManagerFactoryScope scope) { + Long id = scope.fromTransaction( em -> { + MyEntity entity = new MyEntity(); + entity.setText( "initial" ); + em.persist( entity ); + return entity.getId(); + } ); + + RuntimeException simulatedAddToBatchFailure = new RuntimeException( "Simulated RuntimeException" ); + + scope.inTransaction( em -> { + assertThatThrownBy( () -> { + MyEntity entity = em.find( MyEntity.class, id ); + TestBatch.nextAddToBatchFailure.set( simulatedAddToBatchFailure ); + entity.setText( "updated" ); + em.flush(); + } ) + .isSameAs( simulatedAddToBatchFailure ); + + assertAllStatementsAreClosed( testBatch.createdStatements ); + } ); + } + + @Test + public void testRemove(EntityManagerFactoryScope scope) { + Long id = scope.fromTransaction( em -> { + MyEntity entity = new MyEntity(); + entity.setText( "initial" ); + em.persist( entity ); + return entity.getId(); + } ); + + RuntimeException simulatedAddToBatchFailure = new RuntimeException( "Simulated RuntimeException" ); + + scope.inTransaction( em -> { + assertThatThrownBy( () -> { + MyEntity entity = em.find( MyEntity.class, id ); + TestBatch.nextAddToBatchFailure.set( simulatedAddToBatchFailure ); + em.remove( entity ); + em.flush(); + } ) + .isSameAs( simulatedAddToBatchFailure ); + + assertAllStatementsAreClosed( testBatch.createdStatements ); + } ); + } + + 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() ); + } + } ); + } + + @Entity(name = "MyEntity") + public static class MyEntity { + @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; + } + } + + public static class BatchBuilderSettingProvider implements SettingProvider.Provider { + @Override + public String getSetting() { + return TestBatchBuilder.class.getName(); + } + } + + public static class TestBatch extends BatchingBatch { + private static final AtomicReference nextAddToBatchFailure = new AtomicReference<>(); + + private final List createdStatements = new ArrayList<>(); + + public TestBatch(BatchKey key, JdbcCoordinator jdbcCoordinator, int batchSize) { + super( key, jdbcCoordinator, batchSize ); + } + + @Override + public void addToBatch() { + RuntimeException failure = nextAddToBatchFailure.getAndSet( null ); + if ( failure != null ) { + throw failure; + // Implementations really should call abortBatch() before propagating an exception. + // Purposely skipping the call to abortBatch() to ensure that Hibernate works properly when + // an implementation does not call abortBatch(). + } + super.addToBatch(); + } + + @Override + public PreparedStatement getBatchStatement(String sql, boolean callable) { + PreparedStatement batchStatement = super.getBatchStatement( sql, callable ); + createdStatements.add( batchStatement ); + return batchStatement; + } + } + + public static class TestBatchBuilder extends BatchBuilderImpl { + + @Override + public Batch buildBatch(BatchKey key, JdbcCoordinator jdbcCoordinator) { + return buildBatchTest( key, jdbcCoordinator, getJdbcBatchSize() ); + } + + 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/association/GenericAssociationTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/association/GenericAssociationTest.java new file mode 100644 index 000000000000..e513b18c4d85 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/association/GenericAssociationTest.java @@ -0,0 +1,105 @@ +package org.hibernate.orm.test.association; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.MappedSuperclass; +import javax.persistence.OneToOne; + +@TestForIssue(jiraKey = "HHH-16378") +public class GenericAssociationTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Parent.class, + AbstractChild.class, + Child.class + }; + } + + @Test + public void testFindByParentId() { + inTransaction( session -> { + Parent parent = new Parent( 1L ); + Child child = new Child( 2L ); + child.setParent( parent ); + session.persist( parent ); + session.persist( child ); + } ); + + inTransaction( session -> { + assertThat( session.createQuery( "from Child where parent.id = :parentId", Child.class ) + .setParameter( "parentId", 1L ) + .list() ) + .containsExactly( session.getReference( Child.class, 2L ) ); + } ); + } + + @Entity(name = "Parent") + public static class Parent { + @Id + private Long id; + + public Parent() { + } + + public Parent(Long id) { + this.id = id; + } + + public Long getId() { + return this.id; + } + + public void setId(Long id) { + this.id = id; + } + } + + @MappedSuperclass + public abstract static class AbstractChild { + @OneToOne(optional = false) + private T parent; + + public AbstractChild() { + } + + public abstract Long getId(); + + public T getParent() { + return this.parent; + } + + public void setParent(T parent) { + this.parent = parent; + } + } + + @Entity(name = "Child") + public static class Child extends AbstractChild { + @Id + protected Long id; + + public Child() { + } + + public Child(Long id) { + this.id = id; + } + + @Override + public Long getId() { + return this.id; + } + + public void setId(Long id) { + this.id = id; + } + } +} 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 index fa3da3d3caa8..eac0d966ba24 100644 --- 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 @@ -373,6 +373,7 @@ public DataSource getNonJtaDataSource() { integrationSettings.put( AvailableSettings.JPA_JDBC_URL, ConnectionProviderBuilder.URL ); integrationSettings.put( AvailableSettings.JPA_JDBC_USER, ConnectionProviderBuilder.USER ); integrationSettings.put( AvailableSettings.JPA_JDBC_PASSWORD, ConnectionProviderBuilder.PASS ); + integrationSettings.put( "hibernate.connection.init_sql", "" ); final PersistenceProvider provider = new HibernatePersistenceProvider(); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/lazy/NoDirtyCheckingContext.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/lazy/NoDirtyCheckingContext.java new file mode 100644 index 000000000000..e1a194893708 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/lazy/NoDirtyCheckingContext.java @@ -0,0 +1,13 @@ +package org.hibernate.orm.test.bytecode.enhancement.lazy; + +import org.hibernate.bytecode.enhance.spi.UnloadedClass; + +import org.hibernate.testing.bytecode.enhancement.EnhancerTestContext; + +public class NoDirtyCheckingContext extends EnhancerTestContext { + + @Override + public boolean doDirtyCheckingInline(UnloadedClass classDescriptor) { + return false; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/lazy/proxy/crosspackage/CrossPackageMappedSuperclassWithEmbeddableTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/lazy/proxy/crosspackage/CrossPackageMappedSuperclassWithEmbeddableTest.java new file mode 100644 index 000000000000..f037f57bf264 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/lazy/proxy/crosspackage/CrossPackageMappedSuperclassWithEmbeddableTest.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.orm.test.bytecode.enhancement.lazy.proxy.crosspackage; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hibernate.orm.test.bytecode.enhancement.lazy.proxy.crosspackage.base.EmbeddableType; +import org.hibernate.orm.test.bytecode.enhancement.lazy.proxy.crosspackage.derived.TestEntity; + +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.Test; +import org.junit.runner.RunWith; + +@RunWith(BytecodeEnhancerRunner.class) +@EnhancementOptions(lazyLoading = true, inlineDirtyChecking = true) +public class CrossPackageMappedSuperclassWithEmbeddableTest extends BaseNonConfigCoreFunctionalTestCase { + + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { TestEntity.class }; + } + + @Test + @TestForIssue(jiraKey = "HHH-15141") + public void testIt() { + // Just a smoke test; the original failure happened during bytecode enhancement. + Long id = fromTransaction( s -> { + TestEntity testEntity = new TestEntity(); + EmbeddableType embedded = new EmbeddableType(); + embedded.setField( "someValue" ); + testEntity.setEmbeddedField( embedded ); + s.persist( testEntity ); + return testEntity.getId(); + } ); + inTransaction( s -> { + TestEntity testEntity = s.find( TestEntity.class, id ); + assertThat( testEntity.getEmbeddedField().getField() ).isEqualTo( "someValue" ); + } ); + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/lazy/proxy/crosspackage/base/BaseEntity.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/lazy/proxy/crosspackage/base/BaseEntity.java new file mode 100644 index 000000000000..41fd00dcf80f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/lazy/proxy/crosspackage/base/BaseEntity.java @@ -0,0 +1,33 @@ +package org.hibernate.orm.test.bytecode.enhancement.lazy.proxy.crosspackage.base; + +import javax.persistence.Embedded; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.MappedSuperclass; + +@MappedSuperclass +public abstract class BaseEntity { + + @Id + @GeneratedValue + private Long id; + + @Embedded + protected EmbeddableType embeddedField; + + public Long getId() { + return id; + } + + public void setId(final Long id) { + this.id = id; + } + + public EmbeddableType getEmbeddedField() { + return embeddedField; + } + + public void setEmbeddedField(final EmbeddableType embeddedField) { + this.embeddedField = embeddedField; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/lazy/proxy/crosspackage/base/EmbeddableType.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/lazy/proxy/crosspackage/base/EmbeddableType.java new file mode 100644 index 000000000000..12e2eff07dcf --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/lazy/proxy/crosspackage/base/EmbeddableType.java @@ -0,0 +1,19 @@ +package org.hibernate.orm.test.bytecode.enhancement.lazy.proxy.crosspackage.base; + +import javax.persistence.Column; +import javax.persistence.Embeddable; + +@Embeddable +public class EmbeddableType { + + @Column + private String field; + + public String getField() { + return field; + } + + public void setField(final String field) { + this.field = field; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/lazy/proxy/crosspackage/derived/TestEntity.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/lazy/proxy/crosspackage/derived/TestEntity.java new file mode 100644 index 000000000000..965fbf0cb2b4 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/lazy/proxy/crosspackage/derived/TestEntity.java @@ -0,0 +1,10 @@ +package org.hibernate.orm.test.bytecode.enhancement.lazy.proxy.crosspackage.derived; + +import javax.persistence.Entity; + +import org.hibernate.orm.test.bytecode.enhancement.lazy.proxy.crosspackage.base.BaseEntity; + +@Entity +public class TestEntity extends BaseEntity { + +} 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 000000000000..590e7322dc88 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/FkRefTests.java @@ -0,0 +1,254 @@ +/* + * 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.Entity; +import javax.persistence.FetchType; +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.testing.jdbc.SQLStatementInspector; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +/** + * Tests for the new `{fk}` HQL token + * + * @author Steve Ebersole + */ +@DomainModel( annotatedClasses = { FkRefTests.Coin.class, FkRefTests.Currency.class } ) +@SessionFactory( useCollectingStatementInspector = true ) +public class FkRefTests { + + @Test + @JiraKey( "HHH-15106" ) + public void testSimplePredicateUse(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + statementInspector.clear(); + + // there is a Coin which has a currency_fk = 1 + scope.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 ).hasSize( 1 ); + assertThat( coins.get( 0 ) ).isNotNull(); + assertThat( coins.get( 0 ).getCurrency() ).isNull(); + + assertThat( statementInspector.getSqlQueries() ).hasSize( 2 ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " join " ); + } ); + + statementInspector.clear(); + + // However, the "matching" Currency does not exist + scope.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 ).hasSize( 0 ); + } ); + + statementInspector.clear(); + + // check using `currency` as a naked "property-ref" + scope.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 ).hasSize( 1 ); + assertThat( coins.get( 0 ) ).isNotNull(); + assertThat( coins.get( 0 ).getCurrency() ).isNull(); + + assertThat( statementInspector.getSqlQueries() ).hasSize( 2 ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " join " ); + } ); + } + + @Test + @JiraKey( "HHH-15106" ) + public void testNullnessPredicateUse(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + statementInspector.clear(); + + // there is one Coin (id=3) which has a null currency_fk + scope.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 ).hasSize( 1 ); + assertThat( coins.get( 0 ) ).isNotNull(); + assertThat( coins.get( 0 ).getId() ).isEqualTo( 3 ); + assertThat( coins.get( 0 ).getCurrency() ).isNull(); + + assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " join " ); + } ); + + statementInspector.clear(); + + // check using `currency` as a naked "property-ref" + scope.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 ).hasSize( 1 ); + assertThat( coins.get( 0 ) ).isNotNull(); + assertThat( coins.get( 0 ).getId() ).isEqualTo( 3 ); + assertThat( coins.get( 0 ).getCurrency() ).isNull(); + + assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " join " ); + } ); + } + + @Test + @JiraKey( "HHH-15106" ) + public void testFkRefDereferenceNotAllowed(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + statementInspector.clear(); + + scope.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() ).isInstanceOf( QueryException.class ); + } + catch (Exception e) { + fail( "Unexpected failure type : " + e ); + } + } ); + + scope.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() ).isInstanceOf( QueryException.class ); + } + } ); + } + + @BeforeEach + public void prepareTestData(SessionFactoryScope scope) { + scope.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 ); + } ); + + scope.inTransaction( (session) -> { + session.createQuery( "delete Currency where id = 1" ).executeUpdate(); + } ); + } + + @AfterEach + public void cleanupTest(SessionFactoryScope scope) throws Exception { + scope.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" ) + 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/notfound/exception/NotFoundExceptionLogicalOneToOneTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/exception/NotFoundExceptionLogicalOneToOneTest.java new file mode 100644 index 000000000000..b4415802a3b6 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/exception/NotFoundExceptionLogicalOneToOneTest.java @@ -0,0 +1,359 @@ +/* + * 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.exception; + +import java.io.Serializable; +import java.util.List; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToOne; + +import org.hibernate.FetchNotFoundException; +import org.hibernate.Hibernate; +import org.hibernate.ObjectNotFoundException; +import org.hibernate.annotations.NotFound; +import org.hibernate.annotations.NotFoundAction; + +import org.hibernate.testing.jdbc.SQLStatementInspector; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.assertj.core.api.Assertions; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.fail; + +/** + * Tests for `@OneToOne @NotFound(EXCEPTION)` + * + * NOTES:

        + *
      1. `@NotFound` should force the association to be eager - `Coin#currency` should be loaded immediately
      2. + *
      3. When loading the `Coin#currency`, `EXCEPTION` should trigger an exception since the particular `Coin#currency` fk is broken
      4. + *
      + * + * @author Steve Ebersole + */ +@DomainModel( annotatedClasses = { NotFoundExceptionLogicalOneToOneTest.Coin.class, NotFoundExceptionLogicalOneToOneTest.Currency.class } ) +@SessionFactory( useCollectingStatementInspector = true ) +public class NotFoundExceptionLogicalOneToOneTest { + @Test + @JiraKey( "HHH-15060" ) + public void testProxy(SessionFactoryScope scope) { + // test handling of a proxy for the Coin pointing to the missing Currency + scope.inTransaction( (session) -> { + final Coin proxy = session.byId( Coin.class ).getReference( 1 ); + try { + Hibernate.initialize( proxy ); + Assertions.fail( "Expecting FetchNotFoundException" ); + } + catch (ObjectNotFoundException expected) { + assertThat( expected.getEntityName() ).endsWith( "Currency" ); + assertThat( expected.getIdentifier() ).isEqualTo( 1 ); + } + } ); + + // test handling of a proxy for the missing Currency + scope.inTransaction( (session) -> { + final Currency proxy = session.byId( Currency.class ).getReference( 1 ); + try { + Hibernate.initialize( proxy ); + Assertions.fail( "Expecting FetchNotFoundException" ); + } + catch (ObjectNotFoundException expected) { + assertThat( expected.getEntityName() ).endsWith( "Currency" ); + assertThat( expected.getIdentifier() ).isEqualTo( 1 ); + } + } ); + } + + @Test + @JiraKey( "HHH-15060" ) + public void testGet(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + statementInspector.clear(); + + scope.inTransaction( (session) -> { + final Coin coin = session.get( Coin.class, 2 ); + + // most importantly, the currency should not be uninitialized + assertThat( Hibernate.isPropertyInitialized( coin, "currency" ) ) + .describedAs( "Expecting `Coin#currency` to be eagerly fetched (bytecode) due to `@NotFound`" ) + .isTrue(); + assertThat( Hibernate.isInitialized( coin.getCurrency() ) ) + .describedAs( "Expecting `Coin#currency` to be eagerly fetched due to `@NotFound`" ) + .isTrue(); + + // join may be better here. but for now, 5.x generates 2 selects here + // which is not wrong + assertThat( statementInspector.getSqlQueries() ).hasSize( 2 ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " Currency " ); + assertThat( statementInspector.getSqlQueries().get( 1 ) ).doesNotContain( " Coin " ); + } ); + + statementInspector.clear(); + + scope.inTransaction( (session) -> { + try { + session.get( Coin.class, 1 ); + fail( "Expecting ObjectNotFoundException" ); + } + catch (FetchNotFoundException expected) { + assertThat( expected.getEntityName() ).isEqualTo( Currency.class.getName() ); + assertThat( expected.getIdentifier() ).isEqualTo( 1 ); + assertThat( statementInspector.getSqlQueries() ).hasSize( 2 ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " Currency " ); + assertThat( statementInspector.getSqlQueries().get( 1 ) ).doesNotContain( " Coin " ); + } + } ); + } + + @Test + @JiraKey( "HHH-15060" ) + public void testQueryImplicitPathDereferencePredicateBaseline(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + statementInspector.clear(); + + // Baseline for comparison with `#testQueryImplicitPathDereferencePredicate` + // We ultimately want the `.id` reference to behave exactly the same as + // this query - specifically forcing the join + scope.inTransaction( (session) -> { + final String hql = "select c from Coin c where c.currency.name = 'Euro'"; + final List coins = session.createQuery( hql, Coin.class ).getResultList(); + assertThat( coins ).isEmpty(); + } ); + + assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " ); + // unfortunately, versions of Hibernate prior to 6 used restricted cross joins + // (i.e. `x cross join y where x.y_fk = y.id`) to handle implicit query joins + assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " cross " ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " ); + } + + @Test + @JiraKey( "HHH-15060" ) + public void testQueryImplicitPathDereferencePredicateBaseline2(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + statementInspector.clear(); + + scope.inTransaction( (session) -> { + // NOTE : this query is conceptually the same as the one from + // `#testQueryImplicitPathDereferencePredicateBaseline` in that we want + // a join and we want to use the fk target column (here, `Currency.id`) + // rather than the normal perf-opt strategy of using the fk key column + // (here, `Coin.currency_fk`). + final String hql = "select c from Coin c where c.currency.id = 2"; + final List coins = session.createQuery( hql, Coin.class ).getResultList(); + assertThat( coins ).hasSize( 1 ); + + assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " ); + // unfortunately, versions of Hibernate prior to 6 used restricted cross joins + // (i.e. `x cross join y where x.y_fk = y.id`) to handle implicit query joins + assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " cross " ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " ); + } ); + } + + @Test + @JiraKey( "HHH-15060" ) + public void testQueryImplicitPathDereferencePredicateBaseline3(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + statementInspector.clear(); + + scope.inTransaction( (session) -> { + // NOTE : this query is conceptually the same as the one from + // `#testQueryImplicitPathDereferencePredicateBaseline` in that we want + // a join and we want to use the fk target column (here, `Currency.id`) + // rather than the normal perf-opt strategy of using the fk key column + // (here, `Coin.currency_fk`). + final String hql = "select c from Coin c join fetch c.currency c2 where c2.name = 'USD'"; + final List coins = session.createQuery( hql, Coin.class ).getResultList(); + assertThat( coins ).hasSize( 1 ); + assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); + } ); + + statementInspector.clear(); + + scope.inTransaction( (session) -> { + // NOTE : this query is conceptually the same as the one from + // `#testQueryImplicitPathDereferencePredicateBaseline` in that we want + // a join and we want to use the fk target column (here, `Currency.id`) + // rather than the normal perf-opt strategy of using the fk key column + // (here, `Coin.currency_fk`). + final String hql = "select c from Coin c join fetch c.currency c2 where c2.name = 'Euro'"; + final List coins = session.createQuery( hql, Coin.class ).getResultList(); + assertThat( coins ).hasSize( 0 ); + assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); + } ); + } + + @Test + @JiraKey( "HHH-15060" ) +// @FailureExpected( +// reason = "When we have a dangling key (as in the `c.currency.id = 1` case), the outcome " + +// "ought to simply be no results. At the moment, however, FetchNotFoundException is " + +// "thrown. The underlying problem is that we use the FK key rather than the FK " + +// "target for selecting the association" + +// "" + +// " But the correct outcome is " + +// "simply no results. This needs to trigger the join to use the fk target as part " + +// "of the predicate, not the fk value" +// ) + public void testQueryImplicitPathDereferencePredicate(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + statementInspector.clear(); + + scope.inTransaction( (session) -> { + final String hql = "select c from Coin c where c.currency.id = 1"; + final List coins = session.createQuery( hql, Coin.class ).getResultList(); + // there is no Currency with id=1 (Euro) + assertThat( coins ).isEmpty(); + + assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " ); + // unfortunately, versions of Hibernate prior to 6 used restricted cross joins + // (i.e. `x cross join y where x.y_fk = y.id`) to handle implicit query joins + assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " cross " ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " ); + } ); + } + + @Test + @JiraKey( "HHH-15060" ) + public void testQueryOwnerSelection(SessionFactoryScope scope) { + scope.inTransaction( (session) -> { + final String hql = "select c from Coin c where c.id = 1"; + try { + //noinspection unused (debugging) + final Coin coin = session.createQuery( hql, Coin.class ).uniqueResult(); + fail( "Expecting FetchNotFoundException" ); + } + catch (FetchNotFoundException expected) { + assertThat( expected.getEntityName() ).endsWith( "Currency" ); + assertThat( expected.getIdentifier() ).isEqualTo( 1 ); + } + } ); + + scope.inTransaction( (session) -> { + final String hql = "select c from Coin c where c.id = 2"; + final Coin coin = session.createQuery( hql, Coin.class ).uniqueResult(); + assertThat( Hibernate.isPropertyInitialized( coin, "currency" ) ).isTrue(); + assertThat( Hibernate.isInitialized( coin.getCurrency() ) ).isTrue(); + } ); + } + + @BeforeEach + public void prepareTestData(SessionFactoryScope scope) { + scope.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 ); + } ); + + scope.inTransaction( (session) -> { + session.createQuery( "delete Currency where id = 1" ).executeUpdate(); + } ); + } + + @AfterEach + public void cleanupTest(SessionFactoryScope scope) throws Exception { + scope.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.EXCEPTION) + @JoinColumn( name = "currency_fk" ) + 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/notfound/exception/NotFoundExceptionManyToOneTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/exception/NotFoundExceptionManyToOneTest.java new file mode 100644 index 000000000000..813c691c2b06 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/exception/NotFoundExceptionManyToOneTest.java @@ -0,0 +1,273 @@ +/* + * 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.exception; + +import java.io.Serializable; +import java.util.List; + +import org.hibernate.FetchNotFoundException; +import org.hibernate.Hibernate; +import org.hibernate.ObjectNotFoundException; +import org.hibernate.annotations.NotFound; +import org.hibernate.annotations.NotFoundAction; + +import org.hibernate.testing.jdbc.SQLStatementInspector; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.FailureExpected; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import org.assertj.core.api.Assertions; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.fail; + +/** + * Tests for `@ManyToOne @NotFound(EXCEPTION)` + * + * NOTES:
        + *
      1. `@NotFound` should force the association to be eager - `Coin#currency` should be loaded immediately
      2. + *
      3. `EXCEPTION` should trigger an exception since the particular `Coin#currency` fk is broken
      4. + *
      + * + * @author Steve Ebersole + */ +@DomainModel( annotatedClasses = { NotFoundExceptionManyToOneTest.Coin.class, NotFoundExceptionManyToOneTest.Currency.class } ) +@SessionFactory( useCollectingStatementInspector = true ) +public class NotFoundExceptionManyToOneTest { + + @Test + @JiraKey( "HHH-15060" ) + public void testProxy(SessionFactoryScope scope) { + // test handling of a proxy for the Coin pointing to the missing Currency + scope.inTransaction( (session) -> { + final Coin proxy = session.byId( Coin.class ).getReference( 1 ); + try { + Hibernate.initialize( proxy ); + Assertions.fail( "Expecting ObjectNotFoundException" ); + } + catch (ObjectNotFoundException expected) { + assertThat( expected.getEntityName() ).endsWith( "Currency" ); + assertThat( expected.getIdentifier() ).isEqualTo( 1 ); + } + } ); + + scope.inTransaction( (session) -> { + // the non-existent Currency + final Currency proxy = session.byId( Currency.class ).getReference( 1 ); + try { + Hibernate.initialize( proxy ); + Assertions.fail( "Expecting ObjectNotFoundException" ); + } + catch (ObjectNotFoundException expected) { + assertThat( expected.getEntityName() ).endsWith( "Currency" ); + assertThat( expected.getIdentifier() ).isEqualTo( 1 ); + } + } ); + } + + @Test + @JiraKey( "HHH-15060" ) + public void testGet(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + statementInspector.clear(); + + scope.inTransaction( (session) -> { + try { + // should fail here loading the Coin due to missing currency (see NOTE#1) + final Coin coin = session.get( Coin.class, 1 ); + fail( "Expecting ObjectNotFoundException for broken fk" ); + } + catch (FetchNotFoundException expected) { + assertThat( expected.getEntityName() ).isEqualTo( Currency.class.getName() ); + assertThat( expected.getIdentifier() ).isEqualTo( 1 ); + + // atm, 5.x generates 2 selects here; which wouldn't be bad, except that + // the first one contains a join + // + // what "should" happen +// assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); +// assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " ); +// assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " ); + // what actually happens + assertThat( statementInspector.getSqlQueries() ).hasSize( 2 ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Coin " ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Currency " ); + assertThat( statementInspector.getSqlQueries().get( 1 ) ).doesNotContain( " Coin " ); + } + } ); + } + + @Test + @JiraKey( "HHH-15060" ) + public void testQueryImplicitPathDereferencePredicate(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + statementInspector.clear(); + + scope.inTransaction( (session) -> { + final String hql = "select c from Coin c where c.currency.id = 1"; + final List coins = session.createQuery( hql, Coin.class ).getResultList(); + // there is no Currency with id=1 (Euro) + assertThat( coins ).isEmpty(); + + assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " ); + // unfortunately, versions of Hibernate prior to 6 used restricted cross joins + // (i.e. `x cross join y where x.y_fk = y.id`) to handle implicit query joins + assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " cross " ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " ); + } ); + } + + @Test + @JiraKey( "HHH-15060" ) + public void testQueryOwnerSelection(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + statementInspector.clear(); + + scope.inTransaction( (session) -> { + final String hql = "select c from Coin c where c.id = 1"; + try { + session.createQuery( hql, Coin.class ).getResultList(); + fail( "Expecting ObjectNotFoundException for broken fk" ); + } + catch (FetchNotFoundException expected) { + assertThat( expected.getEntityName() ).isEqualTo( Currency.class.getName() ); + assertThat( expected.getIdentifier() ).isEqualTo( 1 ); + + // join may be better here. but for now, 5.x generates 2 selects here + // which is not wrong. + assertThat( statementInspector.getSqlQueries() ).hasSize( 2 ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " Currency " ); + assertThat( statementInspector.getSqlQueries().get( 1 ) ).doesNotContain( " Coin " ); + } + } ); + } + + @Test + @JiraKey( "HHH-15060" ) + public void testQueryAssociationSelection(SessionFactoryScope scope) { + scope.inTransaction( (session) -> { + final String hql = "select c.currency from Coin c where c.id = 1"; + final List currencies = session.createQuery( hql, Currency.class ).getResultList(); + assertThat( currencies ).isEmpty(); + } ); + } + + @BeforeEach + public void prepareTestData(SessionFactoryScope scope) { + scope.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 ); + } ); + + scope.inTransaction( (session) -> { + session.createQuery( "delete Currency where id = 1" ).executeUpdate(); + } ); + } + + @AfterEach + public void cleanupTest(SessionFactoryScope scope) throws Exception { + scope.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; + } + + @ManyToOne(fetch = FetchType.EAGER) + @NotFound(action = NotFoundAction.EXCEPTION) + @JoinColumn( name = "currency_fk" ) + 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/notfound/ignore/IsNullAndNotFoundTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/ignore/IsNullAndNotFoundTest.java new file mode 100644 index 000000000000..e31fcac9c41e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/ignore/IsNullAndNotFoundTest.java @@ -0,0 +1,192 @@ +/* + * 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.notfound.ignore; + +import java.util.List; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.OneToOne; +import javax.persistence.Table; + +import org.hibernate.annotations.NotFound; +import org.hibernate.annotations.NotFoundAction; + +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + + +public class IsNullAndNotFoundTest extends BaseNonConfigCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Account.class, Person.class }; + } + + @Before + public void setUp() { + inTransaction( + session -> { + Account account1 = new Account( 1, null, null ); + Account account2 = new Account( 2, "Fab", null ); + + Person person1 = new Person( 1, "Luigi", account1 ); + Person person2 = new Person( 2, "Andrea", account2 ); + Person person3 = new Person( 3, "Max", null ); + + session.persist( account1 ); + session.persist( account2 ); + session.persist( person1 ); + session.persist( person2 ); + session.persist( person3 ); + } + ); + } + + @After + public void tearDown() { + inTransaction( + session -> { + session.createQuery( "delete from Person" ).executeUpdate(); + session.createQuery( "delete from Account" ).executeUpdate(); + } + ); + } + + @Test + public void testIsNullInWhereClause() { + inTransaction( + session -> { + final List ids = session.createQuery( + "select p.id from Person p where p.account.code is null" ).getResultList(); + + assertEquals( 1, ids.size() ); + assertEquals( 1, (int) ids.get( 0 ) ); + + } + ); + } + + @Test + public void testIsNullInWhereClause2() { + inTransaction( + session -> { + final List ids = session.createQuery( + "select distinct p.id from Person p where p.account is null" ).getResultList(); + + assertEquals( 1, ids.size() ); + assertEquals( 3, (int) ids.get( 0 ) ); + + } + ); + } + + @Test + public void testIsNullInWhereClause3() { + inTransaction( + session -> { + final List ids = session.createQuery( + "select distinct p.id from Person p where p.account is null" ).getResultList(); + + assertEquals( 1, ids.size() ); + assertEquals( 3, (int) ids.get( 0 ) ); + + } + ); + } + + @Test + public void testIsNullInWhereClause4() { + inTransaction( + session -> { + final List ids = session.createQuery( + "select p.id from Person p where p.account.code is null or p.account.id is null" ) + .getResultList(); + + assertEquals( 1, ids.size() ); + assertEquals( 1, (int) ids.get( 0 ) ); + + } + ); + } + + @Test + public void testWhereClause() { + inTransaction( + session -> { + final List ids = session.createQuery( + "select p.id from Person p where p.account.code = :code and p.account.id = :id" ) + .setParameter( "code", "Fab" ) + .setParameter( "id", 2 ) + .getResultList(); + + assertEquals( 1, ids.size() ); + assertEquals( 2, (int) ids.get( 0 ) ); + + } + ); + } + + + @Entity(name = "Person") + public static class Person { + + @Id + private Integer id; + + private String name; + + @OneToOne + @NotFound(action = NotFoundAction.IGNORE) + private Account account; + + Person() { + } + + public Person(Integer id, String name, Account account) { + this.id = id; + this.name = name; + this.account = account; + } + + public Integer getId() { + return id; + } + + public String getName() { + return name; + } + + public Account getAccount() { + return account; + } + } + + @Entity(name = "Account") + @Table(name = "ACCOUNT_TABLE") + public static class Account { + @Id + private Integer id; + + private String code; + + private Double amount; + + public Account() { + } + + public Account(Integer id, String code, Double amount) { + this.id = id; + this.code = code; + this.amount = amount; + } + + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/ignore/NotFoundIgnoreManyToOneTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/ignore/NotFoundIgnoreManyToOneTest.java new file mode 100644 index 000000000000..062da1e17455 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/ignore/NotFoundIgnoreManyToOneTest.java @@ -0,0 +1,307 @@ +/* + * 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.ignore; + +import java.io.Serializable; +import java.util.List; + +import org.hibernate.FetchNotFoundException; +import org.hibernate.Hibernate; +import org.hibernate.ObjectNotFoundException; +import org.hibernate.annotations.NotFound; +import org.hibernate.annotations.NotFoundAction; +import org.hibernate.orm.test.notfound.exception.NotFoundExceptionLogicalOneToOneTest; + +import org.hibernate.testing.jdbc.SQLStatementInspector; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.FailureExpected; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Tuple; +import org.assertj.core.api.Assertions; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for `@ManyToOne @NotFound(IGNORE)` + * + * NOTES:
        + *
      1. `@NotFound` should force the association to be eager - `Coin#currency` should be loaded immediately
      2. + *
      3. `IGNORE` says to treat the broken fk as null
      4. + *
      + * + * @author Steve Ebersole + */ +@DomainModel( annotatedClasses = { NotFoundIgnoreManyToOneTest.Coin.class, NotFoundIgnoreManyToOneTest.Currency.class } ) +@SessionFactory( useCollectingStatementInspector = true ) +public class NotFoundIgnoreManyToOneTest { + + @Test + @JiraKey( "HHH-15060" ) + public void testProxy(SessionFactoryScope scope) { + // test handling of a proxy for the Coin pointing to the missing Currency + scope.inTransaction( (session) -> { + final Coin proxy = session.byId( Coin.class ).getReference( 1 ); + Hibernate.initialize( proxy ); + assertThat( proxy.getCurrency() ).isNull(); + } ); + + scope.inTransaction( (session) -> { + // the non-existent Currency + // - this is the one valid deviation from treating the broken fk as null + final Currency proxy = session.byId( Currency.class ).getReference( 1 ); + try { + Hibernate.initialize( proxy ); + Assertions.fail( "Expecting ObjectNotFoundException" ); + } + catch (ObjectNotFoundException expected) { + assertThat( expected.getEntityName() ).endsWith( "Currency" ); + assertThat( expected.getIdentifier() ).isEqualTo( 1 ); + } + } ); + } + + @Test + @JiraKey( "HHH-15060" ) + public void testGet(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + statementInspector.clear(); + + scope.inTransaction( (session) -> { + final Coin coin = session.get( Coin.class, 1 ); + assertThat( coin.getCurrency() ).isNull(); + + // atm, 5.x generates 2 selects here; which wouldn't be bad, except that + // the first one contains a join + // + // what "should" happen +// assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); +// assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " ); +// assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " ); + // what actually happens + assertThat( statementInspector.getSqlQueries() ).hasSize( 2 ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Coin " ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Currency " ); + assertThat( statementInspector.getSqlQueries().get( 1 ) ).doesNotContain( " Coin " ); + } ); + } + + @Test + @JiraKey( "HHH-15060" ) + public void testQueryImplicitPathDereferencePredicate(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + statementInspector.clear(); + + scope.inTransaction( (session) -> { + final String hql = "select c from Coin c where c.currency.id = 1"; + final List coins = session.createQuery( hql, Coin.class ).getResultList(); + // there is no Currency with id=1 (Euro) + assertThat( coins ).isEmpty(); + + assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " cross " ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " ); + } ); + } + + @Test + @JiraKey( "HHH-15060" ) + public void testQueryOwnerSelection(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + statementInspector.clear(); + + scope.inTransaction( (session) -> { + final String hql = "select c from Coin c"; + final List coins = session.createQuery( hql, Coin.class ).getResultList(); + assertThat( coins ).hasSize( 1 ); + assertThat( coins.get( 0 ).getCurrency() ).isNull(); + + // at the moment this uses a subsequent-select. on the bright side, it is at least eagerly fetched. + assertThat( statementInspector.getSqlQueries() ).hasSize( 2 ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Coin " ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " Currency " ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " join " ); + assertThat( statementInspector.getSqlQueries().get( 1 ) ).doesNotContain( " Coin " ); + assertThat( statementInspector.getSqlQueries().get( 1 ) ).contains( " Currency " ); + assertThat( statementInspector.getSqlQueries().get( 1 ) ).doesNotContain( " join " ); + } ); + } + + @Test + @JiraKey( "HHH-15060" ) + @FailureExpected( + reason = "Has zero results because of inner-join due to being defined in the select-clause. " + + "Not sure the best outcome here - no results or null elements within the results?" + ) + public void testQueryAssociationSelection(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + statementInspector.clear(); + + // I guess this one is somewhat debatable, but for consistency I think this makes the most sense + scope.inTransaction( (session) -> { + final String hql = "select c.currency from Coin c"; + session.createQuery( hql, Currency.class ).getResultList(); + final List currencies = session.createQuery( hql, Currency.class ).getResultList(); + assertThat( currencies ).hasSize( 1 ); + assertThat( currencies.get( 0 ) ).isNull(); + + assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " ); + } ); + } + + @Test + @JiraKey( "HHH-15060" ) + public void testSubqueryUse(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + statementInspector.clear(); + + scope.inTransaction( (session) -> { + final String hql = "select c " + + "from Coin c " + + "where exists (" + + " select 1" + + " from Coin other" + + ")"; + } ); + } + + @Test + @JiraKey( "HHH-15060" ) + @FailureExpected( + reason = "Has zero results because of inner-join due to being defined in the select-clause. " + + "Not sure the best outcome here - no results or null elements within the results?" + ) + public void testQueryAssociationSelection2(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + statementInspector.clear(); + + scope.inTransaction( (session) -> { + final String hql = "select c.id, c.currency from Coin c"; + final List tuples = session.createQuery( hql, Tuple.class ).getResultList(); + assertThat( tuples ).hasSize( 1 ); + final Tuple tuple = tuples.get( 0 ); + assertThat( tuple.get( 0 ) ).isEqualTo( 1 ); + assertThat( tuple.get( 1 ) ).isNull(); + + assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " ); + } ); + } + + @BeforeEach + protected void prepareTestData(SessionFactoryScope scope) { + scope.inTransaction( (session) -> { + Currency euro = new Currency( 1, "Euro" ); + Coin fiveC = new Coin( 1, "Five cents", euro ); + + session.persist( euro ); + session.persist( fiveC ); + } ); + + scope.inTransaction( (session) -> { + session.createQuery( "delete Currency where id = 1" ).executeUpdate(); + } ); + } + + @AfterEach + protected void dropTestData(SessionFactoryScope scope) throws Exception { + scope.inTransaction( (session) -> { + session.createQuery( "delete Coin where id = 1" ).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; + } + + @ManyToOne(fetch = FetchType.EAGER) + @NotFound(action = NotFoundAction.IGNORE) + @JoinColumn( name = "currency_fk" ) + 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/notfound/ignore/NotFoundIgnoreOneToOneTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/ignore/NotFoundIgnoreOneToOneTest.java new file mode 100644 index 000000000000..6e1598dbc30b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/ignore/NotFoundIgnoreOneToOneTest.java @@ -0,0 +1,265 @@ +/* + * 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.ignore; + +import java.io.Serializable; +import java.util.List; + +import org.hibernate.FetchNotFoundException; +import org.hibernate.Hibernate; +import org.hibernate.ObjectNotFoundException; +import org.hibernate.annotations.NotFound; +import org.hibernate.annotations.NotFoundAction; + +import org.hibernate.testing.jdbc.SQLStatementInspector; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.FailureExpected; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToOne; +import javax.persistence.Tuple; +import org.assertj.core.api.Assertions; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for `@OneToOne @NotFound(IGNORE)` + * + * NOTES:
        + *
      1. `@NotFound` should force the association to be eager - `Coin#currency` should be loaded immediately
      2. + *
      3. `IGNORE` says to treat the broken fk as null
      4. + *
      + * + * @author Steve Ebersole + */ +@DomainModel( annotatedClasses = { NotFoundIgnoreOneToOneTest.Coin.class, NotFoundIgnoreOneToOneTest.Currency.class } ) +@SessionFactory( useCollectingStatementInspector = true ) +public class NotFoundIgnoreOneToOneTest { + + @Test + @JiraKey( "HHH-15060" ) + public void testProxy(SessionFactoryScope scope) { + // test handling of a proxy for the Coin pointing to the missing Currency + scope.inTransaction( (session) -> { + final Coin proxy = session.byId( Coin.class ).getReference( 1 ); + Hibernate.initialize( proxy ); + assertThat( proxy.getCurrency() ).isNull(); + } ); + + scope.inTransaction( (session) -> { + // the non-existent Child + // - this is the one valid deviation from treating the broken fk as null + try { + final Currency proxy = session.byId( Currency.class ).getReference( 1 ); + Hibernate.initialize( proxy ); + Assertions.fail( "Expecting ObjectNotFoundException" ); + } + catch (ObjectNotFoundException expected) { + assertThat( expected.getEntityName() ).endsWith( "Currency" ); + assertThat( expected.getIdentifier() ).isEqualTo( 1 ); + } + } ); + } + + @Test + @JiraKey( "HHH-15060" ) + public void testGet(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + statementInspector.clear(); + + scope.inTransaction( (session) -> { + try { + final Coin coin = session.get( Coin.class, 1 ); + } + catch (FetchNotFoundException expected) { + // technically we could use a subsequent-select rather than a join... + assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " ); + } + } ); + } + + @Test + @JiraKey( "HHH-15060" ) + public void testQueryImplicitPathDereferencePredicate(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + statementInspector.clear(); + + scope.inTransaction( (session) -> { + final String hql = "select c from Coin c where c.currency.id = 1"; + final List coins = session.createQuery( hql, Coin.class ).getResultList(); + // there is no Currency with id=1 (Euro) + assertThat( coins ).isEmpty(); + + // technically we could use a subsequent-select rather than a join... + assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " ); + } ); + } + + @Test + @JiraKey( "HHH-15060" ) + public void testQueryOwnerSelection(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + statementInspector.clear(); + + scope.inTransaction( (session) -> { + final String hql = "select c from Coin c"; + final List coins = session.createQuery( hql, Coin.class ).getResultList(); + assertThat( coins ).hasSize( 1 ); + assertThat( coins.get( 0 ).getCurrency() ).isNull(); + + // at the moment this uses a subsequent-select. on the bright side, it is at least eagerly fetched. + assertThat( statementInspector.getSqlQueries() ).hasSize( 2 ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " from Coin " ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " join " ); + assertThat( statementInspector.getSqlQueries().get( 1 ) ).contains( " from Currency " ); + assertThat( statementInspector.getSqlQueries().get( 1 ) ).doesNotContain( " join " ); + } ); + } + + @Test + @JiraKey( "HHH-15060" ) + @FailureExpected( + reason = "Has zero results because of inner-join due to being defined in the select-clause. " + + "Not sure the best outcome here - no results or null elements within the results?" + ) + public void testQueryAssociationSelection(SessionFactoryScope scope) { + scope.inTransaction( (session) -> { + final String hql = "select c.currency from Coin c"; + final List currencies = session.createQuery( hql, Currency.class ).getResultList(); + assertThat( currencies ).hasSize( 1 ); + assertThat( currencies.get( 0 ) ).isNull(); + } ); + } + + @Test + @JiraKey( "HHH-15060" ) + @FailureExpected( + reason = "Has zero results because of inner-join due to being defined in the select-clause. " + + "Not sure the best outcome here - no results or null elements within the results?" + ) + public void testQueryAssociationSelection2(SessionFactoryScope scope) { + scope.inTransaction( (session) -> { + final String hql = "select c.id, c.currency from Coin c"; + final List tuples = session.createQuery( hql, Tuple.class ).getResultList(); + assertThat( tuples ).hasSize( 1 ); + final Tuple tuple = tuples.get( 0 ); + assertThat( tuple.get( 0 ) ).isEqualTo( 1 ); + assertThat( tuple.get( 1 ) ).isNull(); + } ); + } + + @BeforeEach + public void prepareTestData(SessionFactoryScope scope) { + scope.inTransaction( (session) -> { + Currency euro = new Currency( 1, "Euro" ); + Coin fiveC = new Coin( 1, "Five cents", euro ); + + session.persist( euro ); + session.persist( fiveC ); + } ); + + scope.inTransaction( (session) -> { + session.createQuery( "delete Currency where id = 1" ).executeUpdate(); + } ); + } + + @AfterEach + public void dropTestData(SessionFactoryScope scope) { + scope.inTransaction( (session) -> { + session.createQuery( "delete Coin where id = 1" ).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.EAGER) + @NotFound(action = NotFoundAction.IGNORE) + @JoinColumn( name = "currency_fk" ) + 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/property/PropertyAccessStrategyMapTest.java b/hibernate-core/src/test/java/org/hibernate/property/PropertyAccessStrategyMapTest.java index 1571ba547163..534d79caa348 100644 --- a/hibernate-core/src/test/java/org/hibernate/property/PropertyAccessStrategyMapTest.java +++ b/hibernate-core/src/test/java/org/hibernate/property/PropertyAccessStrategyMapTest.java @@ -8,8 +8,8 @@ import java.util.Date; import java.util.HashMap; +import java.util.Map; -import org.hibernate.mapping.Map; import org.hibernate.property.access.internal.PropertyAccessStrategyMapImpl; import org.hibernate.property.access.spi.PropertyAccess; @@ -42,7 +42,7 @@ public void testNonMap() { } catch (IllegalArgumentException e) { assertEquals( - "Expecting class: [org.hibernate.mapping.Map], but containerJavaType is of type: [java.util.Date] for propertyName: [time]", + "Expecting class: [java.util.Map], but containerJavaType is of type: [java.util.Date] for propertyName: [time]", e.getMessage() ); } diff --git a/hibernate-core/src/test/java/org/hibernate/query/GroupByAliasTest.java b/hibernate-core/src/test/java/org/hibernate/query/GroupByAliasTest.java index a4199263c0cc..1aaeebf5d899 100644 --- a/hibernate-core/src/test/java/org/hibernate/query/GroupByAliasTest.java +++ b/hibernate-core/src/test/java/org/hibernate/query/GroupByAliasTest.java @@ -89,8 +89,8 @@ public void testCompoundIdAlias() { List list = doInJPA(this::entityManagerFactory, entityManager -> { return entityManager.createQuery( - "select p.association as id_alias, sum(p.age) " + - "from Person p group by id_alias, p.association.id, p.association.name order by id_alias", Tuple.class) + "select a as id_alias, sum(p.age) " + + "from Person p join p.association a group by id_alias, a.id, a.name order by id_alias", Tuple.class) .getResultList(); }); diff --git a/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/NullPrecedenceTest.java b/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/NullPrecedenceTest.java index 7e5178766687..5db04129b2f4 100644 --- a/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/NullPrecedenceTest.java +++ b/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/NullPrecedenceTest.java @@ -8,9 +8,11 @@ import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Root; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.query.criteria.HibernateCriteriaBuilder; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import org.junit.Assert; import org.junit.Test; @@ -29,6 +31,7 @@ protected Class[] getAnnotatedClasses() { } @Test + @SkipForDialect(value = SybaseASE15Dialect.class, comment = "No support for null precedence on Sybase") public void testNullPrecedence() { doInJPA( this::entityManagerFactory, entityManager -> { entityManager.persist( new Foo( 1L, null ) ); 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 0590ecdea877..6c94ccb85d9e 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 @@ -22,6 +22,7 @@ import org.hibernate.dialect.DerbyDialect; import org.hibernate.dialect.H2Dialect; import org.hibernate.dialect.PostgreSQL81Dialect; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.testing.RequiresDialect; import org.hibernate.testing.SkipForDialect; @@ -67,6 +68,7 @@ public void testCaseClause() { @Test @SkipForDialect(value = DB2Dialect.class, comment = "We would need casts in the case clauses. See HHH-12822.") @SkipForDialect(value = DerbyDialect.class, comment = "Derby requires either casted parameters or literals in the result arms of CASE expressions") + @SkipForDialect(value = SybaseASE15Dialect.class, comment = "Sybase requires either casted parameters or literals in the result arms of CASE expressions") public void testEqualClause() { doInHibernate( this::sessionFactory, session -> { CriteriaBuilder cb = session.getCriteriaBuilder(); @@ -93,6 +95,7 @@ public void testEqualClause() { @TestForIssue(jiraKey = "HHH-13167") @SkipForDialect(value = DB2Dialect.class, comment = "We would need casts in the case clauses. See HHH-12822.") @SkipForDialect(value = DerbyDialect.class, comment = "Derby requires either casted parameters or literals in the result arms of CASE expressions") + @SkipForDialect(value = SybaseASE15Dialect.class, comment = "Sybase requires either casted parameters or literals in the result arms of CASE expressions") public void testMissingElseClause() { doInHibernate( this::sessionFactory, session -> { Event event = new Event(); diff --git a/hibernate-core/src/test/java/org/hibernate/query/hhh14112/HHH14112Test.java b/hibernate-core/src/test/java/org/hibernate/query/hhh14112/HHH14112Test.java index 0f9e69053067..c202d83b516c 100644 --- a/hibernate-core/src/test/java/org/hibernate/query/hhh14112/HHH14112Test.java +++ b/hibernate-core/src/test/java/org/hibernate/query/hhh14112/HHH14112Test.java @@ -36,7 +36,7 @@ protected Class[] getAnnotatedClasses() { @Entity(name = "Super") @Inheritance(strategy = InheritanceType.JOINED) - @Where(clause = "DELETED = false") + @Where(clause = "deleted = false") public static class Super { @Id @GeneratedValue(strategy = GenerationType.AUTO) diff --git a/hibernate-core/src/test/java/org/hibernate/query/hhh14156/HHH14156Test.java b/hibernate-core/src/test/java/org/hibernate/query/hhh14156/HHH14156Test.java index bb943030b221..61dced02c7bc 100644 --- a/hibernate-core/src/test/java/org/hibernate/query/hhh14156/HHH14156Test.java +++ b/hibernate-core/src/test/java/org/hibernate/query/hhh14156/HHH14156Test.java @@ -9,6 +9,7 @@ import org.hibernate.dialect.DerbyDialect; import org.hibernate.dialect.SQLServerDialect; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; @@ -30,6 +31,7 @@ protected Class[] getAnnotatedClasses() { @Test @SkipForDialect(value = SQLServerDialect.class, comment = "SQLServer doesn't support tuple comparisons") @SkipForDialect(value = DerbyDialect.class, comment = "Derby doesn't support tuple comparisons") + @SkipForDialect(value = SybaseASE15Dialect.class, comment = "Sybase doesn't support tuple comparisons") public void testNoExceptionThrown() { inTransaction( session -> session.createQuery( diff --git a/hibernate-core/src/test/java/org/hibernate/serialization/EntityProxySerializationTest.java b/hibernate-core/src/test/java/org/hibernate/serialization/EntityProxySerializationTest.java index 566e3681166a..510ec9c20e65 100644 --- a/hibernate-core/src/test/java/org/hibernate/serialization/EntityProxySerializationTest.java +++ b/hibernate-core/src/test/java/org/hibernate/serialization/EntityProxySerializationTest.java @@ -67,7 +67,7 @@ public void prepare() { final Transaction t = s.beginTransaction(); try { - final Number count = (Number) s.createQuery("SELECT count(ID) FROM SimpleEntity").getSingleResult(); + final Number count = (Number) s.createQuery("SELECT count(e.id) FROM SimpleEntity e").getSingleResult(); if (count.longValue() > 0L) { // entity already added previously return; diff --git a/hibernate-core/src/test/java/org/hibernate/sql/TemplateTest.java b/hibernate-core/src/test/java/org/hibernate/sql/TemplateTest.java index 6ca186643f60..7d0bed9c99ba 100644 --- a/hibernate-core/src/test/java/org/hibernate/sql/TemplateTest.java +++ b/hibernate-core/src/test/java/org/hibernate/sql/TemplateTest.java @@ -107,6 +107,12 @@ public static void closeSessionFactory() { } } + @Test + public void testNullToFunction() { + //Apparently this may happen during HQL parsing given a wrong syntax + FUNCTION_REGISTRY.findSQLFunction( null ); + } + @Test public void testSqlExtractFunction() { String fragment = "extract( year from col )"; diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/DatabaseCreationTimestampNullableColumnTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/DatabaseCreationTimestampNullableColumnTest.java index 51520daf2c6e..dc13c41a5a3f 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/DatabaseCreationTimestampNullableColumnTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/DatabaseCreationTimestampNullableColumnTest.java @@ -18,11 +18,13 @@ import org.hibernate.annotations.Generated; import org.hibernate.annotations.NaturalId; import org.hibernate.annotations.ValueGenerationType; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.tuple.AnnotationValueGeneration; import org.hibernate.tuple.GenerationTiming; import org.hibernate.tuple.ValueGenerator; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import org.junit.Assert; import org.junit.Test; @@ -33,6 +35,7 @@ * @author Vlad Mihalcea */ @TestForIssue( jiraKey = "HHH-11096" ) +@SkipForDialect(value = SybaseASE15Dialect.class, comment = "current_timestamp requires parenthesis which we don't render") public class DatabaseCreationTimestampNullableColumnTest extends BaseEntityManagerFunctionalTestCase { @Override diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/embeddables/EmbeddableWithManyToManyTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/embeddables/EmbeddableWithManyToManyTest.java new file mode 100644 index 000000000000..c66de0eea6f6 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/embeddables/EmbeddableWithManyToManyTest.java @@ -0,0 +1,137 @@ +/* + * 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.embeddables; + +import java.util.Arrays; +import java.util.Set; +import java.util.stream.Collectors; +import javax.persistence.CascadeType; +import javax.persistence.Embeddable; +import javax.persistence.Embedded; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.ManyToMany; +import javax.persistence.Table; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +@TestForIssue(jiraKey = "HHH-15453") +public class EmbeddableWithManyToManyTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Product.class, + User.class + }; + } + + @Test + public void testMerge() { + inTransaction( + session -> { + User user = new User( 1L, "Fab" ); + session.persist( user ); + + Product product = new Product( 2L, "Sugar", new Users( user ) ); + Product mergedProduct = (Product) session.merge( product ); + assertThat( mergedProduct.getUsers().getUsers() ).isNotNull(); + } + ); + + inTransaction( + session -> { + Product product = session.get( Product.class, 2L ); + assertThat( product ).isNotNull(); + assertThat( product.getUsers().getUsers() ).isNotNull(); + } + ); + } + + @Entity(name = "Product") + public static class Product { + @Id + private Long id; + + private String name; + + @Embedded + private Users users; + + public Product() { + } + + public Product(Long id, String name, Users users) { + this.id = id; + this.name = name; + this.users = users; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public Users getUsers() { + return users; + } + } + + @Embeddable + public static class Users { + + @ManyToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE }) + private Set users; + + public Users() { + } + + public Users(User... users) { + this.users = Arrays.stream( users ).collect( Collectors.toSet() ); + } + + public Set getUsers() { + return users; + } + + public void setUsers(Set users) { + this.users = users; + } + } + + @Entity(name = "User") + @Table(name = "USER_TABLE") + public static class User { + @Id + private Long id; + + private String name; + + public User() { + } + + public User(Long id, String name) { + this.id = id; + this.name = name; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + } +} 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 526a7e062d81..a12f4a137f3a 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 @@ -158,7 +158,6 @@ 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(); diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/formula/FormulaNativeQueryTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/formula/FormulaNativeQueryTest.java index 6827671f51f1..fade39e19d9e 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/formula/FormulaNativeQueryTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/formula/FormulaNativeQueryTest.java @@ -9,6 +9,7 @@ import org.hibernate.annotations.Formula; import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.Configuration; +import org.hibernate.query.NativeQuery; import org.hibernate.testing.FailureExpected; import org.hibernate.testing.TestForIssue; @@ -18,6 +19,8 @@ import org.junit.Test; import javax.persistence.*; + +import java.util.Collections; import java.util.List; import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; @@ -69,9 +72,10 @@ public void cleanup() { @Test @FailureExpected( jiraKey = "HHH-7525" ) - public void testNativeQuery() throws Exception { + public void testNativeQueryWithoutFormulaField() throws Exception { doInHibernate( this::sessionFactory, session -> { + // the native query result mapping fails if the formula field is not returned from the query Query query = session.createNativeQuery( "SELECT ft.* FROM foo_table ft", Foo.class ); List list = query.getResultList(); assertEquals( 3, list.size() ); @@ -79,14 +83,55 @@ public void testNativeQuery() throws Exception { ); } + @Test + public void testNativeQueryWithAllFields() throws Exception { + doInHibernate( + this::sessionFactory, session -> { + Query query = session.createNativeQuery( "SELECT ft.*, abs(locationEnd - locationStart) as distance FROM foo_table ft", Foo.class ); + List list = query.getResultList(); + assertEquals( 3, list.size() ); + } + ); + } + + @Test + public void testNativeQueryWithAliasProperties() throws Exception { + doInHibernate( + this::sessionFactory, session -> { + NativeQuery query = session.createNativeQuery( "SELECT ft.*, abs(ft.locationEnd - locationStart) as d FROM foo_table ft"); + query.addRoot( "ft", Foo.class ) + .addProperty( "id", "id" ) + .addProperty( "locationStart", "locationStart" ) + .addProperty( "locationEnd", "locationEnd" ) + .addProperty( "distance", "d" ); + List list = query.getResultList(); + assertEquals( 3, list.size() ); + } + ); + } + + @Test + public void testNativeQueryWithAliasSyntax() throws Exception { + doInHibernate( + this::sessionFactory, session -> { + NativeQuery query = session.createNativeQuery( + "SELECT ft.id as {ft.id}, ft.locationStart as {ft.locationStart}, ft.locationEnd as {ft.locationEnd}, abs(ft.locationEnd - locationStart) as {ft.distance} FROM foo_table ft") + .addEntity( "ft", Foo.class ); + query.setProperties( Collections.singletonMap( "distance", "distance" ) ); + List list = query.getResultList(); + assertEquals( 3, list.size() ); + } + ); + } + @Test public void testHql() throws Exception { - // Show that HQL does work + // Show that HQL works too doInHibernate( this::sessionFactory, session -> { Query query = session.createQuery( "SELECT ft FROM Foo ft", Foo.class ); List list = query.getResultList(); - assertEquals(3, list.size()); + assertEquals( 3, list.size() ); } ); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/formula/JoinFormulaOneToManyNotIgnoreLazyFetchingTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/formula/JoinFormulaOneToManyNotIgnoreLazyFetchingTest.java index eb16c23d6f99..69a1552a7211 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/formula/JoinFormulaOneToManyNotIgnoreLazyFetchingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/formula/JoinFormulaOneToManyNotIgnoreLazyFetchingTest.java @@ -19,23 +19,28 @@ import javax.persistence.ManyToOne; import javax.persistence.OneToMany; +import org.hibernate.Hibernate; import org.hibernate.LazyInitializationException; import org.hibernate.annotations.JoinColumnOrFormula; import org.hibernate.annotations.JoinFormula; import org.hibernate.annotations.NotFound; import org.hibernate.annotations.NotFoundAction; import org.hibernate.cfg.AnnotationBinder; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.logger.LoggerInspectionRule; import org.hibernate.testing.logger.Triggerable; +import org.hibernate.testing.transaction.TransactionUtil; +import org.hibernate.testing.transaction.TransactionUtil2; import org.junit.Rule; import org.junit.Test; import org.jboss.logging.Logger; +import static org.assertj.core.api.Assertions.assertThat; import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -82,25 +87,21 @@ protected void afterEntityManagerFactoryBuilt() { } @Test - public void testLazyLoading() { + public void testLoading() { assertFalse( triggerable.wasTriggered() ); - List stocks = doInJPA( this::entityManagerFactory, entityManager -> { - return entityManager.createQuery( - "SELECT s FROM Stock s", Stock.class ) - .getResultList(); + List stocks = TransactionUtil2.fromTransaction( entityManagerFactory().unwrap( SessionFactoryImplementor.class ), (session) -> { + return session.createQuery("SELECT s FROM Stock s order by id", Stock.class ).getResultList(); } ); - assertEquals( 2, stocks.size() ); - try { - assertEquals( "ABC", stocks.get( 0 ).getCodes().get( 0 ).getRefNumber() ); + assertThat( stocks ).hasSize( 2 ); - fail( "Should have thrown LazyInitializationException" ); - } - catch (LazyInitializationException expected) { + final Stock firstStock = stocks.get( 0 ); + final Stock secondStock = stocks.get( 1 ); - } + assertThat( firstStock.getCodes() ).hasSize( 1 ); + assertThat( secondStock.getCodes() ).hasSize( 0 ); } @Entity(name = "Stock") diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/join/JoinTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/join/JoinTest.java index 6293249f3336..a6a7598355e2 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/join/JoinTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/join/JoinTest.java @@ -19,9 +19,11 @@ import org.hibernate.boot.MetadataBuilder; import org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl; import org.hibernate.criterion.Restrictions; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.exception.ConstraintViolationException; import org.hibernate.mapping.Join; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; import org.junit.Test; @@ -152,6 +154,7 @@ public void testReferenceColumnWithBacktics() throws Exception { } @Test + @SkipForDialect(value = SybaseASE15Dialect.class, comment = "Missing constraint violation extractor") public void testUniqueConstaintOnSecondaryTable() throws Exception { Cat cat = new Cat(); cat.setStoryPart2( "My long story" ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/lob/LobTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/lob/LobTest.java index 9225d3fe9b8f..7b4a8fc29ff3 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/lob/LobTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/lob/LobTest.java @@ -9,13 +9,17 @@ package org.hibernate.test.annotations.lob; +import org.hibernate.dialect.SybaseASE15Dialect; + import org.hibernate.testing.DialectChecks; import org.hibernate.testing.RequiresDialectFeature; +import org.hibernate.testing.SkipForDialect; /** * @author Emmanuel Bernard */ @RequiresDialectFeature(DialectChecks.SupportsExpectedLobUsagePattern.class) +@SkipForDialect( value = SybaseASE15Dialect.class, comment = "jTDS driver doesn't implement binary stream handling") public class LobTest extends AbstractLobTest { @Override protected Class getBookClass() { diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/lob/VersionedLobTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/lob/VersionedLobTest.java index 82cef752bf1d..596cfbf84409 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/lob/VersionedLobTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/lob/VersionedLobTest.java @@ -10,9 +10,12 @@ import org.hibernate.Session; import org.hibernate.Transaction; +import org.hibernate.dialect.SybaseASE15Dialect; + import org.hibernate.testing.DialectChecks; import org.hibernate.testing.FailureExpected; import org.hibernate.testing.RequiresDialectFeature; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import static org.junit.Assert.assertEquals; @@ -22,6 +25,7 @@ * @author Gail Badner */ @RequiresDialectFeature(DialectChecks.SupportsExpectedLobUsagePattern.class) +@SkipForDialect( value = SybaseASE15Dialect.class, comment = "jTDS driver doesn't implement binary stream handling") public class VersionedLobTest extends AbstractLobTest { @Override protected Class getBookClass() { diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/lob/locator/LobLocatorTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/lob/locator/LobLocatorTest.java index c0549163aa3f..892b62d87b55 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/lob/locator/LobLocatorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/lob/locator/LobLocatorTest.java @@ -14,8 +14,11 @@ import org.hibernate.Session; 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.dialect.SybaseASE15Dialect; import org.hibernate.type.descriptor.java.DataHelper; /** @@ -35,6 +38,7 @@ protected Class[] getAnnotatedClasses() { @Test @TestForIssue(jiraKey = "HHH-8193") @RequiresDialectFeature(DialectChecks.UsesInputStreamToInsertBlob.class) + @SkipForDialect( value = SybaseASE15Dialect.class, comment = "jTDS driver doesn't implement binary stream handling") public void testStreamResetBeforeParameterBinding() throws SQLException { final Session session = openSession(); diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/manytoone/OptionalManyToOneMapsIdQueryTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/manytoone/OptionalManyToOneMapsIdQueryTest.java new file mode 100644 index 000000000000..d9106022ebc7 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/manytoone/OptionalManyToOneMapsIdQueryTest.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.manytoone; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.MapsId; +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 OptionalManyToOneMapsIdQueryTest extends BaseNonConfigCoreFunctionalTestCase { + + @Test + public void testManyToOneWithIdNamedId() { + // 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 testManyToOneWithNoIdOrPropNamedId() { + // 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 testManyToOneWithNonIdPropNamedId() { + // 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; + + @ManyToOne(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; + + @ManyToOne(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; + + @ManyToOne(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/manytoone/OptionalManyToOnePKJCQueryTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/manytoone/OptionalManyToOnePKJCQueryTest.java new file mode 100644 index 000000000000..b2b3424be748 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/manytoone/OptionalManyToOnePKJCQueryTest.java @@ -0,0 +1,290 @@ +/* + * 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.manytoone; + +import javax.persistence.ConstraintMode; +import javax.persistence.Entity; +import javax.persistence.ForeignKey; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +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 OptionalManyToOnePKJCQueryTest extends BaseNonConfigCoreFunctionalTestCase { + + @Test + public void testManyToOneWithIdNamedId() { + // 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 testManyToOneWithNoIdOrPropNamedId() { + // 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 testManyToOneWithNonIdPropNamedId() { + // 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; + + @ManyToOne(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; + + @ManyToOne(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; + + @ManyToOne(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/naturalid/NaturalIdOnManyToOne.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/naturalid/NaturalIdOnManyToOne.java index b67557861728..31527b343683 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/naturalid/NaturalIdOnManyToOne.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/naturalid/NaturalIdOnManyToOne.java @@ -1,49 +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.test.annotations.naturalid; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.Id; -import javax.persistence.ManyToOne; - -import org.hibernate.annotations.NaturalId; -import org.hibernate.annotations.NaturalIdCache; - -@Entity -@NaturalIdCache -/** - * Test case for NaturalId annotation - ANN-750 - * - * @author Emmanuel Bernard - * @author Hardy Ferentschik - */ -class NaturalIdOnManyToOne { - - @Id - @GeneratedValue - int id; - - @NaturalId - @ManyToOne - Citizen citizen; - - public int getId() { - return id; - } - - public void setId(int id) { - this.id = id; - } - - public Citizen getCitizen() { - return citizen; - } - - public void setCitizen(Citizen citizen) { - this.citizen = citizen; - } -} +/* + * 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.naturalid; +import javax.persistence.*; + +import org.hibernate.annotations.NaturalId; +import org.hibernate.annotations.NaturalIdCache; + +@Entity +@NaturalIdCache +/** + * Test case for NaturalId annotation - ANN-750 + * + * @author Emmanuel Bernard + * @author Hardy Ferentschik + */ +class NaturalIdOnManyToOne { + + @Id + @GeneratedValue + int id; + + @NaturalId + @ManyToOne(fetch = FetchType.LAZY ) + Citizen citizen; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public Citizen getCitizen() { + return citizen; + } + + public void setCitizen(Citizen citizen) { + this.citizen = citizen; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/naturalid/NaturalIdOnSingleManyToOneTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/naturalid/NaturalIdOnSingleManyToOneTest.java index 1c1f8be165a8..516ce4e7591a 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/naturalid/NaturalIdOnSingleManyToOneTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/naturalid/NaturalIdOnSingleManyToOneTest.java @@ -1,136 +1,178 @@ -/* - * 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.naturalid; - -import java.util.List; - -import org.jboss.logging.Logger; -import org.junit.After; -import org.junit.Test; - -import org.hibernate.Criteria; -import org.hibernate.Hibernate; -import org.hibernate.Session; -import org.hibernate.Transaction; -import org.hibernate.cfg.Configuration; -import org.hibernate.criterion.Restrictions; -import org.hibernate.metadata.ClassMetadata; -import org.hibernate.stat.Statistics; -import org.hibernate.testing.TestForIssue; -import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -/** - * Test case for NaturalId annotation. See ANN-750. - * - * @author Emmanuel Bernard - * @author Hardy Ferentschik - */ -@SuppressWarnings("unchecked") -@TestForIssue( jiraKey = "ANN-750" ) -public class NaturalIdOnSingleManyToOneTest extends BaseCoreFunctionalTestCase { - - @After - public void cleanupData() { - super.cleanupCache(); - Session s = sessionFactory().openSession(); - s.beginTransaction(); - s.createQuery( "delete NaturalIdOnManyToOne" ).executeUpdate(); - s.createQuery( "delete Citizen" ).executeUpdate(); - s.createQuery( "delete State" ).executeUpdate(); - s.getTransaction().commit(); - s.close(); - } - - @Test - public void testMappingProperties() { - log.warn("Commented out test"); - - ClassMetadata metaData = sessionFactory().getClassMetadata( - NaturalIdOnManyToOne.class - ); - assertTrue( - "Class should have a natural key", metaData - .hasNaturalIdentifier() - ); - int[] propertiesIndex = metaData.getNaturalIdentifierProperties(); - assertTrue( "Wrong number of elements", propertiesIndex.length == 1 ); - } - - @Test - public void testManyToOneNaturalIdCached() { - NaturalIdOnManyToOne singleManyToOne = new NaturalIdOnManyToOne(); - Citizen c1 = new Citizen(); - c1.setFirstname( "Emmanuel" ); - c1.setLastname( "Bernard" ); - c1.setSsn( "1234" ); - - State france = new State(); - france.setName( "Ile de France" ); - c1.setState( france ); - - singleManyToOne.setCitizen( c1 ); - - Session s = openSession(); - Transaction tx = s.beginTransaction(); - s.persist( france ); - s.persist( c1 ); - s.persist( singleManyToOne ); - tx.commit(); - s.close(); - - s.getSessionFactory().getCache().evictNaturalIdRegions(); - Statistics stats = sessionFactory().getStatistics(); - stats.setStatisticsEnabled( true ); - stats.clear(); - assertEquals( "NaturalId cache puts should be zero", 0, stats.getNaturalIdCachePutCount() ); - assertEquals( "NaturalId cache hits should be zero", 0, stats.getNaturalIdCacheHitCount() ); - assertEquals( "NaturalId Cache Puts", 0, stats.getNaturalIdCachePutCount() ); - assertEquals( "NaturalId cache misses should be zero", 0, stats.getNaturalIdCacheMissCount() ); - - s = openSession(); - tx = s.beginTransaction(); - Criteria criteria = s.createCriteria( NaturalIdOnManyToOne.class ); - criteria.add( Restrictions.naturalId().set( "citizen", c1 ) ); - criteria.setCacheable( true ); - - // first query - List results = criteria.list(); - assertEquals( 1, results.size() ); - assertEquals( "NaturalId Cache Hits", 0, stats.getNaturalIdCacheHitCount() ); - assertEquals( "NaturalId Cache Misses", 1, stats.getNaturalIdCacheMissCount() ); - assertEquals( "NaturalId Cache Puts", 2, stats.getNaturalIdCachePutCount() ); // one for Citizen, one for NaturalIdOnManyToOne - assertEquals( "NaturalId Cache Queries", 1, stats.getNaturalIdQueryExecutionCount() ); - - // query a second time - result should be in session cache - criteria.list(); - assertEquals( "NaturalId Cache Hits", 0, stats.getNaturalIdCacheHitCount() ); - assertEquals( "NaturalId Cache Misses", 1, stats.getNaturalIdCacheMissCount() ); - assertEquals( "NaturalId Cache Puts", 2, stats.getNaturalIdCachePutCount() ); - assertEquals( "NaturalId Cache Queries", 1, stats.getNaturalIdQueryExecutionCount() ); - - // cleanup - tx.rollback(); - s.close(); - } - - @Override - protected Class[] getAnnotatedClasses() { - return new Class[] { - Citizen.class, State.class, - NaturalIdOnManyToOne.class - }; - } - - @Override - protected void configure(Configuration cfg) { - cfg.setProperty( "hibernate.cache.use_query_cache", "true" ); - } -} +/* + * 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.naturalid; + +import java.util.List; + +import org.jboss.logging.Logger; +import org.junit.After; +import org.junit.Test; + +import org.hibernate.Criteria; +import org.hibernate.Hibernate; +import org.hibernate.Session; +import org.hibernate.Transaction; +import org.hibernate.cfg.Configuration; +import org.hibernate.criterion.Restrictions; +import org.hibernate.metadata.ClassMetadata; +import org.hibernate.stat.Statistics; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; + +import static org.junit.Assert.*; + +/** + * Test case for NaturalId annotation. See ANN-750. + * + * @author Emmanuel Bernard + * @author Hardy Ferentschik + */ +@SuppressWarnings("unchecked") +@TestForIssue( jiraKey = "ANN-750" ) +public class NaturalIdOnSingleManyToOneTest extends BaseCoreFunctionalTestCase { + + @After + public void cleanupData() { + super.cleanupCache(); + Session s = sessionFactory().openSession(); + s.beginTransaction(); + s.createQuery( "delete NaturalIdOnManyToOne" ).executeUpdate(); + s.createQuery( "delete Citizen" ).executeUpdate(); + s.createQuery( "delete State" ).executeUpdate(); + s.getTransaction().commit(); + s.close(); + } + + @Test + public void testMappingProperties() { + log.warn("Commented out test"); + + ClassMetadata metaData = sessionFactory().getClassMetadata( + NaturalIdOnManyToOne.class + ); + assertTrue( + "Class should have a natural key", metaData + .hasNaturalIdentifier() + ); + int[] propertiesIndex = metaData.getNaturalIdentifierProperties(); + assertTrue( "Wrong number of elements", propertiesIndex.length == 1 ); + } + + @Test + public void testManyToOneNaturalIdCached() { + NaturalIdOnManyToOne singleManyToOne = new NaturalIdOnManyToOne(); + Citizen c1 = new Citizen(); + c1.setFirstname( "Emmanuel" ); + c1.setLastname( "Bernard" ); + c1.setSsn( "1234" ); + + State france = new State(); + france.setName( "Ile de France" ); + c1.setState( france ); + + singleManyToOne.setCitizen( c1 ); + + Session s = openSession(); + Transaction tx = s.beginTransaction(); + s.persist( france ); + s.persist( c1 ); + s.persist( singleManyToOne ); + tx.commit(); + s.close(); + + s.getSessionFactory().getCache().evictNaturalIdRegions(); + Statistics stats = sessionFactory().getStatistics(); + stats.setStatisticsEnabled( true ); + stats.clear(); + assertEquals( "NaturalId cache puts should be zero", 0, stats.getNaturalIdCachePutCount() ); + assertEquals( "NaturalId cache hits should be zero", 0, stats.getNaturalIdCacheHitCount() ); + assertEquals( "NaturalId Cache Puts", 0, stats.getNaturalIdCachePutCount() ); + assertEquals( "NaturalId cache misses should be zero", 0, stats.getNaturalIdCacheMissCount() ); + + s = openSession(); + tx = s.beginTransaction(); + Criteria criteria = s.createCriteria( NaturalIdOnManyToOne.class ); + criteria.add( Restrictions.naturalId().set( "citizen", c1 ) ); + criteria.setCacheable( true ); + + // first query + List results = criteria.list(); + assertEquals( 1, results.size() ); + assertEquals( "NaturalId Cache Hits", 0, stats.getNaturalIdCacheHitCount() ); + assertEquals( "NaturalId Cache Misses", 1, stats.getNaturalIdCacheMissCount() ); + assertEquals( "NaturalId Cache Puts", 1, stats.getNaturalIdCachePutCount() ); // one for NaturalIdOnManyToOne + assertEquals( "NaturalId Cache Queries", 1, stats.getNaturalIdQueryExecutionCount() ); + + // query a second time - result should be in session cache + criteria.list(); + assertEquals( "NaturalId Cache Hits", 0, stats.getNaturalIdCacheHitCount() ); + assertEquals( "NaturalId Cache Misses", 1, stats.getNaturalIdCacheMissCount() ); + assertEquals( "NaturalId Cache Puts", 1, stats.getNaturalIdCachePutCount() ); + assertEquals( "NaturalId Cache Queries", 1, stats.getNaturalIdQueryExecutionCount() ); + + // cleanup + tx.rollback(); + s.close(); + } + + @Test + @TestForIssue( jiraKey = "HHH-14943") + public void testManyToOneNaturalLoadByNaturalId() { + NaturalIdOnManyToOne singleManyToOne1 = new NaturalIdOnManyToOne(); + NaturalIdOnManyToOne singleManyToOne2 = new NaturalIdOnManyToOne(); + + Citizen c1 = new Citizen(); + c1.setFirstname( "Emmanuel" ); + c1.setLastname( "Bernard" ); + c1.setSsn( "1234" ); + + State france = new State(); + france.setName( "Ile de France" ); + c1.setState( france ); + + singleManyToOne1.setCitizen( c1 ); + singleManyToOne2.setCitizen( null ); + + inTransaction( + session -> { + session.persist( france ); + session.persist( c1 ); + session.persist( singleManyToOne1 ); + session.persist( singleManyToOne2 ); + } + ); + + // we want to go to the db + sessionFactory().getCache().evictNaturalIdData(); + + inTransaction( + session -> { +// NaturalIdOnManyToOne instance1 = session.byNaturalId(NaturalIdOnManyToOne.class).using("citizen",c1).load(); +// assertNotNull(instance1); +// assertNotNull(instance1.getCitizen()); + + NaturalIdOnManyToOne instance2 = session.byNaturalId(NaturalIdOnManyToOne.class).using("citizen", null).load(); + + assertNotNull(instance2); + assertNull(instance2.getCitizen()); + } + ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Citizen.class, State.class, + NaturalIdOnManyToOne.class + }; + } + + @Override + protected void configure(Configuration cfg) { + cfg.setProperty( "hibernate.cache.use_query_cache", "true" ); + } +} 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 000000000000..c7dbae580e65 --- /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/notfound/NotFoundLogicalOneToOneTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/notfound/NotFoundLogicalOneToOneTest.java index c940b3faecc6..8352fd95e8d4 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/notfound/NotFoundLogicalOneToOneTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/notfound/NotFoundLogicalOneToOneTest.java @@ -91,7 +91,7 @@ public void setName(String name) { } @OneToOne(fetch = FetchType.LAZY) - @JoinColumn(foreignKey = @ForeignKey(value = ConstraintMode.NO_CONSTRAINT)) +// @JoinColumn(foreignKey = @ForeignKey(value = ConstraintMode.NO_CONSTRAINT)) @NotFound(action = NotFoundAction.IGNORE) public Currency getCurrency() { return currency; diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/OneToManyJoinColumnsUniquenessTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/OneToManyJoinColumnsUniquenessTest.java new file mode 100644 index 000000000000..d2d05152101a --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/OneToManyJoinColumnsUniquenessTest.java @@ -0,0 +1,115 @@ +package org.hibernate.test.annotations.onetomany; + +import java.io.Serializable; +import java.util.Set; +import javax.persistence.Column; +import javax.persistence.Embeddable; +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinColumns; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.PersistenceException; + +import org.hibernate.cfg.Configuration; +import org.hibernate.cfg.Environment; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.jdbc.SQLStatementInspector; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +import static org.junit.Assert.fail; + +@TestForIssue(jiraKey = "HHH-15091") +public class OneToManyJoinColumnsUniquenessTest extends BaseCoreFunctionalTestCase { + private static final SQLStatementInspector statementInspector = new SQLStatementInspector(); + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + EntityA.class, + EntityB.class + }; + } + + @Override + protected void configure(Configuration configuration) { + super.configure( configuration ); + configuration.getProperties().put( Environment.STATEMENT_INSPECTOR, statementInspector ); + } + + @Test + public void testInsertWithNullAssociationThrowPersistenceException() { + statementInspector.clear(); + + inTransaction( + session -> { + try { + EntityB entityB = new EntityB( 1l ); + session.persist( entityB ); + fail("PersistenceException expected"); + } + catch (PersistenceException e) { + //expected + } + // check that no insert statement has bees executed + statementInspector.assertExecutedCount( 0 ); + } + ); + } + + @Entity(name = "EntityA") + public static class EntityA { + + @EmbeddedId + private PK id; + + @OneToMany(mappedBy = "entityA", fetch = FetchType.LAZY) + private Set entityBs; + + public EntityA() { + } + } + + @Embeddable + public static class PK implements Serializable { + @Column(name = "id_1") + private String id1; + @Column(name = "id_2") + private String id2; + + public PK() { + } + + public PK(String id1, String id2) { + this.id1 = id1; + this.id2 = id2; + } + } + + @Entity(name = "EntityB") + public static class EntityB { + @Id + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumns(value = { + @JoinColumn(name = "b_to_a_1", referencedColumnName = "id_1", nullable = false) + , + @JoinColumn(name = "b_to_a_2", referencedColumnName = "id_2", nullable = false) + } + ) + private EntityA entityA; + + public EntityB() { + } + + public EntityB(Long id) { + this.id = id; + } + } +} 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 0027981c70c4..0feead73c71b 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 @@ -32,6 +32,7 @@ import org.hibernate.boot.MetadataSources; import org.hibernate.boot.registry.StandardServiceRegistry; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.exception.ConstraintViolationException; import org.hibernate.mapping.Column; import org.hibernate.mapping.PersistentClass; @@ -39,6 +40,7 @@ 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.BaseNonConfigCoreFunctionalTestCase; import org.hibernate.test.annotations.Customer; @@ -138,6 +140,7 @@ public void testListWithBagSemanticAndOrderBy() throws Exception { } @Test + @SkipForDialect(value = SybaseASE15Dialect.class, comment = "Missing constraint violation extractor") public void testUnidirectionalDefault() throws Exception { Session s; Transaction tx; diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/OrderByTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/OrderByTest.java index 52870917a8ca..fca691cbfa32 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/OrderByTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/OrderByTest.java @@ -30,6 +30,7 @@ import org.hibernate.dialect.MySQLDialect; import org.hibernate.dialect.Oracle8iDialect; import org.hibernate.dialect.SQLServer2008Dialect; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.graph.RootGraph; import org.hibernate.persister.collection.CollectionPersister; @@ -37,6 +38,7 @@ import org.hibernate.query.Query; import org.hibernate.sql.SimpleSelect; import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.Assert; @@ -49,6 +51,7 @@ */ public class OrderByTest extends BaseCoreFunctionalTestCase { @Test + @SkipForDialect(value = SybaseASE15Dialect.class, comment = "Didn't check what's wrong, but probably null ordering") public void testOrderByOnIdClassProperties() throws Exception { Session s = openSession( ); s.getTransaction().begin(); @@ -419,6 +422,7 @@ public void testInverseIndexCascaded() { @Test @TestForIssue(jiraKey = "HHH-8794") + @SkipForDialect(value = SybaseASE15Dialect.class, comment = "Didn't check what's wrong, but probably null ordering") public void testOrderByNoElement() { final Session s = openSession(); @@ -451,6 +455,7 @@ public void testOrderByNoElement() { @Test @TestForIssue( jiraKey = "HHH-9002" ) + @SkipForDialect(value = SybaseASE15Dialect.class, comment = "Didn't check what's wrong, but probably null ordering") public void testOrderByOneToManyWithJoinTable() { A a = new A(); a.setName( "a" ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/EmbeddedIdTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/EmbeddedIdTest.java new file mode 100644 index 000000000000..8f387271f38a --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/EmbeddedIdTest.java @@ -0,0 +1,85 @@ +/* + * 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 java.io.Serializable; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; + +import javax.persistence.CascadeType; +import javax.persistence.Embeddable; +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; +import javax.persistence.JoinColumn; +import javax.persistence.OneToOne; +import javax.persistence.Table; + +import org.junit.Test; + +@TestForIssue(jiraKey = "HHH-15235") +public class EmbeddedIdTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Bar.class, Foo.class }; + } + + @Test + public void testMerge() { + inTransaction( + session -> { + FooId fooId = new FooId(); + fooId.id = "foo"; + Foo foo = new Foo(); + foo.id = fooId; + Bar bar = new Bar(); + BarId barId = new BarId(); + barId.id = 1l; + bar.id = barId; + foo.bar = bar; + bar.foo = foo; + session.merge( foo ); + } + ); + } + + @Embeddable + public static class BarId implements Serializable { + private Long id; + } + + @Embeddable + public static class FooId implements Serializable { + private String id; + } + + @Entity(name = "Bar") + @Table(name = "BAR_TABLE") + public static class Bar { + @EmbeddedId + private BarId id; + + private String name; + + @OneToOne(mappedBy = "bar") + private Foo foo; + } + + @Entity(name = "Foo") + @Table(name = "FOO_TABLE") + public static class Foo { + @EmbeddedId + private FooId id; + + private String name; + + @OneToOne(cascade = CascadeType.ALL) + @JoinColumn(name = "bar_id") + private Bar bar; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/OverrideOneToOneJoinColumnTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/OverrideOneToOneJoinColumnTest.java new file mode 100644 index 000000000000..087e02fb8bf7 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/OverrideOneToOneJoinColumnTest.java @@ -0,0 +1,204 @@ +package org.hibernate.test.annotations.onetoone; + +import javax.persistence.AssociationOverride; +import javax.persistence.Embeddable; +import javax.persistence.Embedded; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.MappedSuperclass; +import javax.persistence.OneToOne; + +import org.hibernate.AnnotationException; +import org.hibernate.boot.Metadata; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.model.naming.Identifier; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.mapping.ForeignKey; +import org.hibernate.mapping.Table; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseUnitTestCase; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +/** + * @author Aresnii Skvortsov + */ +@TestForIssue(jiraKey = "HHH-4384") +public class OverrideOneToOneJoinColumnTest extends BaseUnitTestCase { + + @Test + public void allowIfJoinColumnIsAbsent() { + StandardServiceRegistry ssr = new StandardServiceRegistryBuilder().build(); + try { + Metadata metadata = new MetadataSources( ssr ) + .addAnnotatedClass( Person.class ) + .addAnnotatedClass( State.class ) + .buildMetadata(); + + Table personTable = metadata.getDatabase().getDefaultNamespace().locateTable( Identifier.toIdentifier( + "PERSON_TABLE" ) ); + ForeignKey foreignKey = personTable.getForeignKeyIterator().next(); + + assertEquals( + "Overridden join column name should be applied", + "PERSON_ADDRESS_STATE", + foreignKey.getColumn( 0 ).getName() + ); + + } + finally { + StandardServiceRegistryBuilder.destroy( ssr ); + } + } + + @Test + public void disallowOnSideWithMappedBy() { + StandardServiceRegistry ssr = new StandardServiceRegistryBuilder().build(); + try { + new MetadataSources( ssr ) + .addAnnotatedClass( Employee.class ) + .addAnnotatedClass( PartTimeEmployee.class ) + .addAnnotatedClass( Desk.class ) + .buildMetadata(); + fail( "Should disallow @JoinColumn override on side with mappedBy" ); + } + catch (AnnotationException ex) { + assertTrue( + "Should disallow exactly because of @JoinColumn override on side with mappedBy", + ex + .getMessage() + .startsWith( "Illegal attempt to define a @JoinColumn with a mappedBy association:" ) + ); + } + finally { + StandardServiceRegistryBuilder.destroy( ssr ); + } + } + + @Entity(name = "Person") + @javax.persistence.Table(name = "PERSON_TABLE") + public static class Person { + + private String id; + + private Address address; + + @Id + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + @Embedded + @AssociationOverride(name = "state", joinColumns = { @JoinColumn(name = "PERSON_ADDRESS_STATE") }) + public Address getAddress() { + return address; + } + + public void setAddress(Address address) { + this.address = address; + } + } + + @Embeddable + public static class Address { + + private String street; + + private String city; + + private State state; + + @OneToOne + public State getState() { + return state; + } + + public void setState(State state) { + this.state = state; + } + + public String getStreet() { + return street; + } + + public void setStreet(String street) { + this.street = street; + } + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + } + + @Entity(name = "State") + @javax.persistence.Table(name = "STATE_TABLE") + public static class State { + + private String id; + + private String name; + + @Id + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + @MappedSuperclass + public static class Employee { + + @Id + private Long id; + + private String name; + + @OneToOne(mappedBy = "employee") + protected Desk desk; + } + + @Entity + @AssociationOverride(name = "desk", + joinColumns = @JoinColumn(name = "PARTTIMEEMPLOYEE_DESK")) + public static class PartTimeEmployee extends Employee { + + } + + @Entity(name = "Desk") + public static class Desk { + @Id + private Long id; + + @OneToOne + private PartTimeEmployee employee; + + private String location; + } + + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/hhh9798/OneToOneJoinTableTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/hhh9798/OneToOneJoinTableTest.java index cdef9869ce66..3311df899279 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/hhh9798/OneToOneJoinTableTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/hhh9798/OneToOneJoinTableTest.java @@ -10,11 +10,13 @@ import org.hibernate.Session; import org.hibernate.Transaction; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.exception.ConstraintViolationException; import org.hibernate.id.IdentifierGenerationException; import org.junit.Test; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; @@ -25,6 +27,7 @@ public class OneToOneJoinTableTest extends BaseCoreFunctionalTestCase { @Test + @SkipForDialect(value = SybaseASE15Dialect.class, comment = "Missing constraint violation extractor") public void storeNonUniqueRelationship() throws Throwable { Session session = null; try { 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 a11b3e8e8c21..7d8542874e18 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 @@ -30,10 +30,10 @@ import org.hibernate.dialect.PostgreSQL9Dialect; import org.hibernate.dialect.PostgreSQLDialect; import org.hibernate.dialect.PostgresPlusDialect; -import org.hibernate.dialect.SybaseDialect; import org.hibernate.dialect.function.SQLFunction; import org.hibernate.stat.Statistics; import org.hibernate.type.StandardBasicTypes; + import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; @@ -121,7 +121,6 @@ public void testNativeQueryWithFormulaAttributeWithoutAlias() { @SkipForDialect(value = PostgreSQL9Dialect.class, jiraKey = "HHH-10312", comment = "Cannot convert untyped null (assumed to be bytea type) to bigint") @SkipForDialect(value = PostgresPlusDialect.class, jiraKey = "HHH-10312", comment = "Cannot convert untyped null (assumed to be bytea type) to bigint") @SkipForDialect(value = CockroachDB192Dialect.class, jiraKey = "HHH-10312", comment = "Cannot convert untyped null (assumed to be bytea type) to bigint") - @SkipForDialect(value = SybaseDialect.class, comment = "Null == null on Sybase") @SkipForDialect(value = DerbyDialect.class, comment = "Cannot convert untyped null (assumed to be VARBINARY type) to BIGINT") public void testQueryWithNullParameter(){ Chaos c0 = new Chaos(); @@ -163,7 +162,6 @@ public void testQueryWithNullParameter(){ @Test @TestForIssue( jiraKey = "HHH-10161") - @SkipForDialect(value = SybaseDialect.class, comment = "Null == null on Sybase") public void testQueryWithNullParameterTyped(){ Chaos c0 = new Chaos(); c0.setId( 0L ); @@ -208,7 +206,6 @@ public void testQueryWithNullParameterTyped(){ @SkipForDialect(value = PostgreSQL9Dialect.class, jiraKey = "HHH-10312", comment = "Cannot convert untyped null (assumed to be bytea type) to bigint") @SkipForDialect(value = PostgresPlusDialect.class, jiraKey = "HHH-10312", comment = "Cannot convert untyped null (assumed to be bytea type) to bigint") @SkipForDialect(value = CockroachDB192Dialect.class, jiraKey = "HHH-10312", comment = "Cannot convert untyped null (assumed to be bytea type) to bigint") - @SkipForDialect(value = SybaseDialect.class, comment = "Null == null on Sybase") @SkipForDialect(value = DerbyDialect.class, comment = "Cannot convert untyped null (assumed to be VARBINARY type) to BIGINT") public void testNativeQueryWithNullParameter(){ Chaos c0 = new Chaos(); @@ -250,7 +247,6 @@ public void testNativeQueryWithNullParameter(){ @Test @TestForIssue( jiraKey = "HHH-10161") - @SkipForDialect(value = SybaseDialect.class, comment = "Null == null on Sybase") public void testNativeQueryWithNullParameterTyped(){ Chaos c0 = new Chaos(); c0.setId( 0L ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/uniqueconstraint/UniqueConstraintThrowsConstraintViolationExceptionTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/uniqueconstraint/UniqueConstraintThrowsConstraintViolationExceptionTest.java index 7393215cdcba..2351b093e8be 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/uniqueconstraint/UniqueConstraintThrowsConstraintViolationExceptionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/uniqueconstraint/UniqueConstraintThrowsConstraintViolationExceptionTest.java @@ -17,8 +17,10 @@ import org.hibernate.cfg.Configuration; import org.hibernate.cfg.Environment; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.exception.ConstraintViolationException; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.Test; @@ -39,6 +41,7 @@ protected Class[] getAnnotatedClasses() { } @Test + @SkipForDialect(value = SybaseASE15Dialect.class, comment = "Missing constraint violation extractor") public void testUniqueConstraintWithEmptyColumnName() { doInHibernate( this::sessionFactory, session -> { Customer customer1 = new Customer(); diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/various/OneOneGeneratedValueTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/various/OneOneGeneratedValueTest.java new file mode 100644 index 000000000000..0810e562c2c1 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/various/OneOneGeneratedValueTest.java @@ -0,0 +1,117 @@ +package org.hibernate.test.annotations.various; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.OneToOne; +import javax.persistence.PrimaryKeyJoinColumn; +import javax.persistence.Table; + +import org.hibernate.annotations.Generated; +import org.hibernate.annotations.GenerationTime; +import org.hibernate.annotations.Subselect; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +@TestForIssue(jiraKey = "HHH-15520") +public class OneOneGeneratedValueTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + EntityA.class, + EntityB.class + }; + } + + @Test + public void testIt() { + inTransaction( + session -> { + EntityA entityA = new EntityA( 1l ); + session.persist( entityA ); + } + ); + inTransaction( + session -> { + EntityA entityA = session.get( EntityA.class, 1l ); + assertThat( entityA ).isNotNull(); + EntityB entityB = entityA.getB(); + assertThat( entityB ).isNotNull(); + assertThat( entityB.getB() ).isEqualTo( 5l ); + } + ); + } + + @Entity(name = "EntityA") + @Table(name = "TABLE_A") + public static class EntityA { + + @Id + private Long id; + + private String name; + + @Generated(GenerationTime.INSERT) + @OneToOne(mappedBy = "a") + private EntityB b; + + public EntityA() { + } + + public EntityA(Long id) { + this.id = id; + } + + public Long getId() { + return id; + } + + public EntityB getB() { + return b; + } + } + + @Entity(name = "EntityB") + @Subselect("SELECT 5 as b, a.id AS AId FROM TABLE_A a") + public static class EntityB { + + private Long aId; + + private EntityA a; + + private Long b; + + @Id + public Long getAId() { + return aId; + } + + public void setAId(Long aId) { + this.aId = aId; + } + + @OneToOne + @PrimaryKeyJoinColumn + public EntityA getA() { + return a; + } + + public void setA(EntityA a) { + this.a = a; + } + + public Long getB() { + return b; + } + + public void setB(Long b) { + this.b = b; + } + + + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/various/OneToOneOptimisticLockTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/various/OneToOneOptimisticLockTest.java new file mode 100644 index 000000000000..1781df98fa48 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/various/OneToOneOptimisticLockTest.java @@ -0,0 +1,119 @@ +/* + * 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.various; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.OneToOne; +import javax.persistence.Table; +import javax.persistence.Version; + +import org.hibernate.annotations.OptimisticLock; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.hibernate.testing.transaction.TransactionUtil2; +import org.junit.Before; +import org.junit.Test; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +@TestForIssue(jiraKey = "HHH-15440") +public class OneToOneOptimisticLockTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Parent.class, Child.class }; + } + + public final static Integer PARENT_ID = 1; + + @Before + public void setUp() { + inTransaction( + session -> { + Parent parent = new Parent( PARENT_ID ); + session.persist( parent ); + } + ); + } + + @Test + public void testUpdateChildDoesNotIncrementParentVersion() { + Integer version = TransactionUtil2.fromTransaction( + sessionFactory(), + session -> { + Parent parent = session.get( Parent.class, PARENT_ID ); + Integer vers = parent.getVersion(); + + Child child = new Child( 2 ); + parent.addChild( child ); + + session.persist( child ); + return vers; + } + ); + + inTransaction( + session -> { + Parent parent = session.get( Parent.class, PARENT_ID ); + assertThat( parent.getVersion() ).isEqualTo( version ); + } + ); + } + + @Entity(name = "Parent") + @Table(name = "PARENT_TABLE") + public static class Parent { + + @Id + Integer id; + + public Parent(Integer id) { + this.id = id; + } + + public Parent() { + } + + @OptimisticLock(excluded = true) + @OneToOne(mappedBy = "parent") + Child child; + + @Version + @Column(name = "VERSION_COLUMN") + Integer version; + + public void addChild(Child child) { + this.child = child; + child.parent = this; + } + + public Integer getVersion() { + return version; + } + } + + @Entity(name = "Child") + @Table(name = "CHILD_TABLE") + public static class Child { + + @Id + Integer id; + + @OneToOne + Parent parent; + + public Child() { + } + + public Child(Integer id) { + this.id = id; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/batch/BatchNoUseJdbcMetadataTest.java b/hibernate-core/src/test/java/org/hibernate/test/batch/BatchNoUseJdbcMetadataTest.java new file mode 100644 index 000000000000..c0d34d31baaa --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/batch/BatchNoUseJdbcMetadataTest.java @@ -0,0 +1,82 @@ +package org.hibernate.test.batch; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; +import org.hibernate.cfg.Environment; +import org.hibernate.dialect.H2Dialect; + +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.jdbc.SQLStatementInspector; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +@TestForIssue(jiraKey = "HHH-15281") +@RequiresDialect(H2Dialect.class) +public class BatchNoUseJdbcMetadataTest extends BaseCoreFunctionalTestCase { + + private final SQLStatementInspector sqlStatementInterceptor = new SQLStatementInspector(); + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Person.class }; + } + + @Override + protected void configure(Configuration configuration) { + configuration.setProperty( AvailableSettings.STATEMENT_BATCH_SIZE, "5" ); + configuration.setProperty( "hibernate.temp.use_jdbc_metadata_defaults", "false" ); + configuration.getProperties().put( Environment.STATEMENT_INSPECTOR, sqlStatementInterceptor ); + } + + @Test + public void testBatching() { + sqlStatementInterceptor.clear(); + inTransaction( + session -> { + for ( int i = 0; i < 11; i++ ) { + Person entity = new Person(); + entity.setId( i ); + entity.setName( Integer.toString( i ) ); + session.persist( entity ); + } + } + ); + sqlStatementInterceptor.assertExecutedCount( 1 ); + inSession( + session -> + assertThat( session.getConfiguredJdbcBatchSize() ).isEqualTo( 5 ) + ); + } + + @Entity(name = "Person") + @Table(name = "PERSON_TABLE") + public static class Person { + + @Id + private Integer id; + + private String name; + + public Integer getId() { + return id; + } + + public void setId(Integer oid) { + this.id = oid; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/boot/database/qualfiedTableNaming/DefaultCatalogAndSchemaTest.java b/hibernate-core/src/test/java/org/hibernate/test/boot/database/qualifiedTableNaming/DefaultCatalogAndSchemaTest.java similarity index 91% rename from hibernate-core/src/test/java/org/hibernate/test/boot/database/qualfiedTableNaming/DefaultCatalogAndSchemaTest.java rename to hibernate-core/src/test/java/org/hibernate/test/boot/database/qualifiedTableNaming/DefaultCatalogAndSchemaTest.java index 09c84f2a8975..a23b1ac04684 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/boot/database/qualfiedTableNaming/DefaultCatalogAndSchemaTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/boot/database/qualifiedTableNaming/DefaultCatalogAndSchemaTest.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.test.boot.database.qualfiedTableNaming; +package org.hibernate.test.boot.database.qualifiedTableNaming; import static org.assertj.core.api.Assertions.assertThat; @@ -52,10 +52,14 @@ import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.Environment; +import org.hibernate.dialect.Dialect; import org.hibernate.dialect.MariaDB102Dialect; import org.hibernate.dialect.MariaDB10Dialect; import org.hibernate.dialect.MariaDB53Dialect; +import org.hibernate.dialect.MySQLDialect; import org.hibernate.dialect.SQLServer2012Dialect; +import org.hibernate.dialect.SQLServerDialect; +import org.hibernate.dialect.SybaseDialect; import org.hibernate.engine.config.spi.ConfigurationService; import org.hibernate.engine.jdbc.env.spi.NameQualifierSupport; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -63,7 +67,6 @@ import org.hibernate.hql.spi.id.local.LocalTemporaryTableBulkIdStrategy; import org.hibernate.id.IdentifierGenerator; import org.hibernate.persister.entity.AbstractEntityPersister; -import org.hibernate.persister.entity.EntityPersister; import org.hibernate.service.spi.ServiceRegistryImplementor; import org.hibernate.tool.hbm2ddl.SchemaExport; import org.hibernate.tool.schema.TargetType; @@ -76,6 +79,7 @@ import org.hibernate.testing.AfterClassOnce; import org.hibernate.testing.BeforeClassOnce; import org.hibernate.testing.SkipForDialect; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.jdbc.SharedDriverManagerConnectionProviderImpl; import org.hibernate.testing.junit4.CustomParameterized; @@ -84,7 +88,9 @@ import org.junit.runners.Parameterized; @RunWith(CustomParameterized.class) -@TestForIssue(jiraKey = { "HHH-14921", "HHH-14922" }) +@TestForIssue(jiraKey = { "HHH-14921", "HHH-14922", "HHH-15212", "HHH-16177" }) +@SkipForDialect(value = SybaseDialect.class, comment = "Sybase doesn't support sequences") +@SkipForDialect(value = MySQLDialect.class, comment = "MySQL doesn't support sequences") @SkipForDialect(value = MariaDB53Dialect.class, strictMatching = true, comment = "MariaDB < 10.3 doesn't support sequences") @SkipForDialect(value = MariaDB10Dialect.class, strictMatching = true, @@ -97,6 +103,8 @@ public class DefaultCatalogAndSchemaTest { private static final String EXPLICIT_CATALOG = "someExplicitCatalog"; private static final String EXPLICIT_SCHEMA = "someExplicitSchema"; + private static final String IMPLICIT_FILE_LEVEL_CATALOG = "someImplicitFileLevelCatalog"; + private static final String IMPLICIT_FILE_LEVEL_SCHEMA = "someImplicitFileLevelSchema"; // Yes this is invalid SQL, and in most cases it simply wouldn't work because of missing columns, // but in this case we don't care: we just want to check catalog/schema substitution. @@ -213,6 +221,10 @@ public void initSessionFactory() { final MetadataSources metadataSources = new MetadataSources( serviceRegistry ); metadataSources.addInputStream( getClass().getResourceAsStream( "implicit-file-level-catalog-and-schema.orm.xml" ) ); metadataSources.addInputStream( getClass().getResourceAsStream( "implicit-file-level-catalog-and-schema.hbm.xml" ) ); + metadataSources.addInputStream( getClass().getResourceAsStream( "no-file-level-catalog-and-schema.orm.xml" ) ); + metadataSources.addInputStream( getClass().getResourceAsStream( "no-file-level-catalog-and-schema.hbm.xml" ) ); + metadataSources.addInputStream( getClass().getResourceAsStream( "database-object-using-catalog-placeholder.hbm.xml" ) ); + metadataSources.addInputStream( getClass().getResourceAsStream( "database-object-using-schema-placeholder.hbm.xml" ) ); if ( configuredXmlMappingPath != null ) { metadataSources.addInputStream( getClass().getResourceAsStream( configuredXmlMappingPath ) ); } @@ -281,6 +293,7 @@ private StandardServiceRegistry createStandardServiceRegistry(String defaultCata final Map settings = new HashMap<>(); settings.put( GlobalTemporaryTableBulkIdStrategy.DROP_ID_TABLES, "true" ); settings.put( LocalTemporaryTableBulkIdStrategy.DROP_ID_TABLES, "true" ); + settings.put( AvailableSettings.JAKARTA_HBM2DDL_CREATE_SCHEMAS, "true" ); if ( !Environment.getProperties().containsKey( Environment.CONNECTION_PROVIDER ) ) { settings.put( AvailableSettings.CONNECTION_PROVIDER, @@ -304,24 +317,40 @@ private StandardServiceRegistry createStandardServiceRegistry(String defaultCata @Test public void createSchema_fromSessionFactory() { String script = generateScriptFromSessionFactory( "create" ); + verifyDDLCreateCatalogOrSchema( script ); + verifyDDLQualifiers( script ); + } + + @Test + @SkipForDialect(value = { SQLServerDialect.class, SybaseDialect.class }, + comment = "SQL Server and Sybase support catalogs but their implementation of DatabaseMetaData" + + " throws exceptions when calling getSchemas/getTables with a non-existing catalog," + + " which results in nasty errors when generating an update script" + + " and some catalogs don't exist.") + public void updateSchema_fromSessionFactory() { + String script = generateScriptFromSessionFactory( "update" ); + verifyDDLCreateCatalogOrSchema( script ); verifyDDLQualifiers( script ); } @Test public void dropSchema_fromSessionFactory() { String script = generateScriptFromSessionFactory( "drop" ); + verifyDDLDropCatalogOrSchema( script ); verifyDDLQualifiers( script ); } @Test public void createSchema_fromMetadata() { String script = generateScriptFromMetadata( SchemaExport.Action.CREATE ); + verifyDDLCreateCatalogOrSchema( script ); verifyDDLQualifiers( script ); } @Test public void dropSchema_fromMetadata() { String script = generateScriptFromMetadata( SchemaExport.Action.DROP ); + verifyDDLDropCatalogOrSchema( script ); verifyDDLQualifiers( script ); } @@ -331,6 +360,8 @@ public void entityPersister() { verifyEntityPersisterQualifiers( EntityWithExplicitQualifiers.class, expectedExplicitQualifier() ); verifyEntityPersisterQualifiers( EntityWithOrmXmlImplicitFileLevelQualifiers.class, expectedImplicitFileLevelQualifier() ); verifyEntityPersisterQualifiers( EntityWithHbmXmlImplicitFileLevelQualifiers.class, expectedImplicitFileLevelQualifier() ); + verifyEntityPersisterQualifiers( EntityWithOrmXmlNoFileLevelQualifiers.class, expectedDefaultQualifier() ); + verifyEntityPersisterQualifiers( EntityWithHbmXmlNoFileLevelQualifiers.class, expectedDefaultQualifier() ); verifyEntityPersisterQualifiers( EntityWithJoinedInheritanceWithDefaultQualifiers.class, expectedDefaultQualifier() ); verifyEntityPersisterQualifiers( EntityWithJoinedInheritanceWithDefaultQualifiersSubclass.class, expectedDefaultQualifier() ); @@ -541,6 +572,46 @@ private T idGenerator(Class expectedType, Cla return expectedType.cast( persister.getIdentifierGenerator() ); } + private void verifyDDLCreateCatalogOrSchema(String sql) { + Dialect dialect = sessionFactory.getJdbcServices().getDialect(); + + if ( sessionFactory.getJdbcServices().getDialect().canCreateCatalog() ) { + assertThat( sql ).contains( dialect.getCreateCatalogCommand( EXPLICIT_CATALOG ) ); + assertThat( sql ).contains( dialect.getCreateCatalogCommand( IMPLICIT_FILE_LEVEL_CATALOG ) ); + if ( expectedDefaultCatalog != null ) { + assertThat( sql ).contains( dialect.getCreateCatalogCommand( expectedDefaultCatalog ) ); + } + } + + if ( sessionFactory.getJdbcServices().getDialect().canCreateSchema() ) { + assertThat( sql ).contains( dialect.getCreateSchemaCommand( EXPLICIT_SCHEMA ) ); + assertThat( sql ).contains( dialect.getCreateSchemaCommand( IMPLICIT_FILE_LEVEL_SCHEMA ) ); + if ( expectedDefaultSchema != null ) { + assertThat( sql ).contains( dialect.getCreateSchemaCommand( expectedDefaultSchema ) ); + } + } + } + + private void verifyDDLDropCatalogOrSchema(String sql) { + Dialect dialect = sessionFactory.getJdbcServices().getDialect(); + + if ( sessionFactory.getJdbcServices().getDialect().canCreateCatalog() ) { + assertThat( sql ).contains( dialect.getDropCatalogCommand( EXPLICIT_CATALOG ) ); + assertThat( sql ).contains( dialect.getDropCatalogCommand( IMPLICIT_FILE_LEVEL_CATALOG ) ); + if ( expectedDefaultCatalog != null ) { + assertThat( sql ).contains( dialect.getDropCatalogCommand( expectedDefaultCatalog ) ); + } + } + + if ( sessionFactory.getJdbcServices().getDialect().canCreateSchema() ) { + assertThat( sql ).contains( dialect.getDropSchemaCommand( EXPLICIT_SCHEMA ) ); + assertThat( sql ).contains( dialect.getDropSchemaCommand( IMPLICIT_FILE_LEVEL_SCHEMA ) ); + if ( expectedDefaultSchema != null ) { + assertThat( sql ).contains( dialect.getDropSchemaCommand( expectedDefaultSchema ) ); + } + } + } + private void verifyDDLQualifiers(String sql) { // Here, to simplify assertions, we assume: // - that all entity types have a table name identical to the entity name @@ -552,6 +623,8 @@ private void verifyDDLQualifiers(String sql) { verifyOnlyQualifier( sql, SqlType.DDL, EntityWithDefaultQualifiers.NAME, expectedDefaultQualifier() ); verifyOnlyQualifier( sql, SqlType.DDL, EntityWithOrmXmlImplicitFileLevelQualifiers.NAME, expectedImplicitFileLevelQualifier() ); verifyOnlyQualifier( sql, SqlType.DDL, EntityWithHbmXmlImplicitFileLevelQualifiers.NAME, expectedImplicitFileLevelQualifier() ); + verifyOnlyQualifier( sql, SqlType.DDL, EntityWithOrmXmlNoFileLevelQualifiers.NAME, expectedDefaultQualifier() ); + verifyOnlyQualifier( sql, SqlType.DDL, EntityWithHbmXmlNoFileLevelQualifiers.NAME, expectedDefaultQualifier() ); verifyOnlyQualifier( sql, SqlType.DDL, EntityWithJoinedInheritanceWithDefaultQualifiers.NAME, expectedDefaultQualifier() ); verifyOnlyQualifier( sql, SqlType.DDL, EntityWithJoinedInheritanceWithDefaultQualifiersSubclass.NAME, expectedDefaultQualifier() ); @@ -581,6 +654,15 @@ private void verifyDDLQualifiers(String sql) { verifyOnlyQualifier( sql, SqlType.DDL, EntityWithExplicitQualifiersWithEnhancedSequenceGenerator.NAME, expectedExplicitQualifier() ); verifyOnlyQualifier( sql, SqlType.DDL, EntityWithDefaultQualifiersWithLegacySequenceGenerator.NAME, expectedDefaultQualifier() ); verifyOnlyQualifier( sql, SqlType.DDL, EntityWithExplicitQualifiersWithLegacySequenceGenerator.NAME, expectedExplicitQualifier() ); + + if ( dbSupportsCatalogs && expectedDefaultCatalog != null ) { + verifyOnlyQualifier( sql, SqlType.DDL, "catalogPrefixedAuxObject", + expectedQualifier( expectedDefaultCatalog, null ) ); + } + if ( dbSupportsSchemas && expectedDefaultSchema != null ) { + verifyOnlyQualifier( sql, SqlType.DDL, "schemaPrefixedAuxObject", + expectedQualifier( null, expectedDefaultSchema ) ); + } } private enum SqlType { @@ -645,7 +727,7 @@ private ExpectedQualifier expectedExplicitQualifier() { } private ExpectedQualifier expectedImplicitFileLevelQualifier() { - return expectedQualifier( "someImplicitFileLevelCatalog", "someImplicitFileLevelSchema" ); + return expectedQualifier( IMPLICIT_FILE_LEVEL_CATALOG, IMPLICIT_FILE_LEVEL_SCHEMA ); } private ExpectedQualifier expectedQualifier(String catalog, String schema) { @@ -813,6 +895,21 @@ public static class EntityWithHbmXmlImplicitFileLevelQualifiers { private String basic; } + public static class EntityWithOrmXmlNoFileLevelQualifiers { + public static final String NAME = "EntityWithOrmXmlNoFileLevelQualifiers"; + private Long id; + private String basic; + private List oneToMany; + private List manyToMany; + private List elementCollection; + } + + public static class EntityWithHbmXmlNoFileLevelQualifiers { + public static final String NAME = "EntityWithHbmXmlNoFileLevelQualifiers"; + private Long id; + private String basic; + } + @Entity(name = EntityWithJoinedInheritanceWithDefaultQualifiers.NAME) @Inheritance(strategy = InheritanceType.JOINED) public static class EntityWithJoinedInheritanceWithDefaultQualifiers { diff --git a/hibernate-core/src/test/java/org/hibernate/test/boot/database/qualfiedTableNaming/NamespaceTest.java b/hibernate-core/src/test/java/org/hibernate/test/boot/database/qualifiedTableNaming/NamespaceTest.java similarity index 97% rename from hibernate-core/src/test/java/org/hibernate/test/boot/database/qualfiedTableNaming/NamespaceTest.java rename to hibernate-core/src/test/java/org/hibernate/test/boot/database/qualifiedTableNaming/NamespaceTest.java index 7ecfd501bb0c..bbef2a3a8c8b 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/boot/database/qualfiedTableNaming/NamespaceTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/boot/database/qualifiedTableNaming/NamespaceTest.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.boot.database.qualfiedTableNaming; +package org.hibernate.test.boot.database.qualifiedTableNaming; import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.naming.PhysicalNamingStrategy; diff --git a/hibernate-core/src/test/java/org/hibernate/test/boot/database/qualfiedTableNaming/QualifiedTableNamingTest.java b/hibernate-core/src/test/java/org/hibernate/test/boot/database/qualifiedTableNaming/QualifiedTableNamingTest.java similarity index 98% rename from hibernate-core/src/test/java/org/hibernate/test/boot/database/qualfiedTableNaming/QualifiedTableNamingTest.java rename to hibernate-core/src/test/java/org/hibernate/test/boot/database/qualifiedTableNaming/QualifiedTableNamingTest.java index 237a947599dc..db306277a149 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/boot/database/qualfiedTableNaming/QualifiedTableNamingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/boot/database/qualifiedTableNaming/QualifiedTableNamingTest.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.boot.database.qualfiedTableNaming; +package org.hibernate.test.boot.database.qualifiedTableNaming; import java.sql.Connection; import java.sql.SQLException; diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/OverriddenFieldTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/OverriddenFieldTest.java new file mode 100644 index 000000000000..2b300dd3545d --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/OverriddenFieldTest.java @@ -0,0 +1,95 @@ +package org.hibernate.test.bytecode.enhancement.basic; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.MappedSuperclass; +import javax.persistence.Table; + +/** + * Tests persisting and then loading a property with bytecode enhancement enabled + * when the entity has the same field defined twice: once in a mappedsuperclass (should be ignored) + * and once in the concrete entity class. + */ +@TestForIssue(jiraKey = "HHH-15505") +@RunWith(BytecodeEnhancerRunner.class) +public class OverriddenFieldTest extends BaseCoreFunctionalTestCase { + + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { AbstractEntity.class, Fruit.class }; + } + + @Before + public void prepare() { + } + + @Test + public void test() { + doInHibernate( this::sessionFactory, s -> { + Fruit testEntity = new Fruit(); + testEntity.setId( 1 ); + testEntity.setName( "John" ); + s.persist( testEntity ); + } ); + + doInHibernate( this::sessionFactory, s -> { + Fruit testEntity = s.get( Fruit.class, 1 ); + Assert.assertEquals( "John", testEntity.getName() ); + } ); + } + + @MappedSuperclass + public static class AbstractEntity { + + @Column(length = 40, unique = true) + private String name; + + } + + @Entity + @Table(name = "known_fruits") + public static class Fruit extends AbstractEntity { + + @Id + private Integer id; + + @Column(length = 40, unique = true) + private String name; + + public Fruit() { + } + + public Fruit(String name) { + 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; + } + + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyLoadingAndInheritanceTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyLoadingAndInheritanceTest.java new file mode 100644 index 000000000000..5127a77b7e5b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyLoadingAndInheritanceTest.java @@ -0,0 +1,109 @@ +/* + * 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 static org.assertj.core.api.Assertions.assertThat; +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; + +import org.hibernate.Hibernate; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; + +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 javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +@RunWith(BytecodeEnhancerRunner.class) +@TestForIssue(jiraKey = "HHH-15090") +public class LazyLoadingAndInheritanceTest extends BaseCoreFunctionalTestCase { + + private Long containingID; + + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { Containing.class, Contained.class, ContainedExtended.class }; + } + + @Override + protected void configure(Configuration configuration) { + configuration.setProperty( AvailableSettings.USE_SECOND_LEVEL_CACHE, "false" ); + configuration.setProperty( AvailableSettings.ENABLE_LAZY_LOAD_NO_TRANS, "true" ); + } + + @Before + public void prepare() { + doInHibernate( this::sessionFactory, s -> { + Containing containing = new Containing(); + ContainedExtended contained = new ContainedExtended( "George" ); + containing.contained = contained; + s.persist( contained ); + s.persist( containing ); + containingID = containing.id; + } ); + } + + @Test + public void test() { + doInHibernate( this::sessionFactory, s -> { + Containing containing = s.load( Containing.class, containingID ); + Contained contained = containing.contained; + assertThat( contained ).isNotNull(); + assertThat( Hibernate.isPropertyInitialized( contained, "name" ) ).isFalse(); + assertThat( contained.name ).isNotNull(); + } ); + } + + @Entity(name = "Containing") + private static class Containing { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + public Long id; + + @ManyToOne(fetch = FetchType.LAZY, optional = false) + public Contained contained; + } + + @Entity(name = "Contained") + private static class Contained { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + public Long id; + + public String name; + + Contained() { + } + + Contained(String name) { + this.name = name; + } + } + + @Entity(name = "ContainedExtended") + private static class ContainedExtended extends Contained { + + ContainedExtended() { + } + + ContainedExtended(String name) { + this.name = name; + } + + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyLoadingByEnhancerSetterTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyLoadingByEnhancerSetterTest.java index b4114a59060d..a569cb1c5677 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyLoadingByEnhancerSetterTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyLoadingByEnhancerSetterTest.java @@ -8,7 +8,10 @@ import org.hibernate.cfg.Configuration; import org.hibernate.cfg.Environment; +import org.hibernate.dialect.SybaseASE15Dialect; + import org.hibernate.testing.FailureExpected; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; @@ -38,6 +41,7 @@ @TestForIssue( jiraKey = "HHH-10747" ) @RunWith( BytecodeEnhancerRunner.class ) +@SkipForDialect( value = SybaseASE15Dialect.class, comment = "jTDS driver doesn't implement binary stream handling") public class LazyLoadingByEnhancerSetterTest extends BaseCoreFunctionalTestCase { private Item item, mergedItem; diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/basic/EagerAndLazyBasicUpdateTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/basic/EagerAndLazyBasicUpdateTest.java new file mode 100644 index 000000000000..b9b9a3cc3b03 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/basic/EagerAndLazyBasicUpdateTest.java @@ -0,0 +1,430 @@ +/* + * 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.basic; + +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; +import org.hibernate.orm.test.bytecode.enhancement.lazy.NoDirtyCheckingContext; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +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.jdbc.SQLStatementInspector; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import javax.persistence.Basic; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Table; + +@RunWith(BytecodeEnhancerRunner.class) +@CustomEnhancementContext( {EnhancerTestContext.class, NoDirtyCheckingContext.class} ) +@TestForIssue(jiraKey = { "HHH-15634", "HHH-16049" }) +public class EagerAndLazyBasicUpdateTest extends BaseCoreFunctionalTestCase { + + private Long entityId; + + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { LazyEntity.class }; + } + + @Override + protected void prepareBasicRegistryBuilder(StandardServiceRegistryBuilder serviceRegistryBuilder) { + super.prepareBasicRegistryBuilder( serviceRegistryBuilder ); + serviceRegistryBuilder.applySetting( AvailableSettings.STATEMENT_INSPECTOR, SQLStatementInspector.class.getName() ); + } + + SQLStatementInspector statementInspector() { + return (SQLStatementInspector) sessionFactory().getSessionFactoryOptions().getStatementInspector(); + } + + private void initNull() { + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = new LazyEntity(); + s.persist( entity ); + entityId = entity.getId(); + } ); + } + + private void initNonNull() { + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = new LazyEntity(); + entity.setEagerProperty( "eager_initial" ); + entity.setLazyProperty1( "lazy1_initial" ); + entity.setLazyProperty2( "lazy2_initial" ); + s.persist( entity ); + entityId = entity.getId(); + } ); + } + + @Before + public void clearStatementInspector() { + statementInspector().clear(); + } + + @Test + public void updateOneLazyProperty_nullToNull() { + initNull(); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + entity.setLazyProperty1( null ); + } ); + + // When a lazy property is modified Hibernate does not perform any select + // but during flush an update is performed + statementInspector().assertUpdate(); + } + + @Test + public void updateOneLazyProperty_nullToNonNull() { + initNull(); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + entity.setLazyProperty1( "lazy1_update" ); + } ); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + assertEquals( "lazy1_update", entity.getLazyProperty1() ); + + assertNull( entity.getEagerProperty() ); + assertNull( entity.getLazyProperty2() ); + } ); + } + + @Test + public void updateOneLazyProperty_nonNullToNonNull_differentValues() { + initNonNull(); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + entity.setLazyProperty1( "lazy1_update" ); + } ); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + assertEquals( "lazy1_update", entity.getLazyProperty1() ); + + assertEquals( "eager_initial", entity.getEagerProperty() ); + assertEquals( "lazy2_initial", entity.getLazyProperty2() ); + } ); + } + + @Test + public void updateOneLazyProperty_nonNullToNonNull_sameValues() { + initNonNull(); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + entity.setLazyProperty1( "lazy1_update" ); + } ); + + // When a lazy property is modified Hibernate does not perform any select + // but during flush an update is performed + statementInspector().assertUpdate(); + } + + @Test + public void updateOneLazyProperty_nonNullToNull() { + initNonNull(); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + entity.setLazyProperty1( null ); + } ); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + assertNull( entity.getLazyProperty1() ); + + assertEquals( "eager_initial", entity.getEagerProperty() ); + assertEquals( "lazy2_initial", entity.getLazyProperty2() ); + } ); + } + + @Test + public void updateOneEagerProperty_nullToNull() { + initNull(); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + entity.setEagerProperty( null ); + } ); + + // We should not update entities when property values did not change + statementInspector().assertNoUpdate(); + } + + @Test + public void updateOneEagerProperty_nullToNonNull() { + initNull(); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + entity.setEagerProperty( "eager_update" ); + } ); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + assertEquals( "eager_update", entity.getEagerProperty() ); + + assertNull( entity.getLazyProperty1() ); + assertNull( entity.getLazyProperty2() ); + } ); + } + + @Test + public void updateOneEagerProperty_nonNullToNonNull_differentValues() { + initNonNull(); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + entity.setEagerProperty( "eager_update" ); + } ); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + assertEquals( "eager_update", entity.getEagerProperty() ); + + assertEquals( "lazy1_initial", entity.getLazyProperty1() ); + assertEquals( "lazy2_initial", entity.getLazyProperty2() ); + } ); + } + + @Test + public void updateOneEagerProperty_nonNullToNonNull_sameValues() { + initNonNull(); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + entity.setEagerProperty( "eager_initial" ); + } ); + + // We should not update entities when property values did not change + statementInspector().assertNoUpdate(); + } + + @Test + public void updateOneEagerProperty_nonNullToNull() { + initNonNull(); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + entity.setEagerProperty( null ); + } ); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + assertNull( entity.getEagerProperty() ); + + assertEquals( "lazy1_initial", entity.getLazyProperty1() ); + assertEquals( "lazy2_initial", entity.getLazyProperty2() ); + } ); + } + + @Test + public void updateOneEagerPropertyAndOneLazyProperty_nullToNull() { + initNull(); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + entity.setEagerProperty( null ); + entity.setLazyProperty1( null ); + } ); + + // When a lazy property is modified Hibernate does not perform any select + // but during flush an update is performed + statementInspector().assertUpdate(); + } + + @Test + public void updateOneEagerPropertyAndOneLazyProperty_nullToNonNull() { + initNull(); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + entity.setEagerProperty( "eager_update" ); + entity.setLazyProperty1( "lazy1_update" ); + } ); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + assertEquals( "eager_update", entity.getEagerProperty() ); + assertEquals( "lazy1_update", entity.getLazyProperty1() ); + + assertNull( entity.getLazyProperty2() ); + } ); + } + + @Test + public void updateOneEagerPropertyAndOneLazyProperty_nonNullToNonNull_differentValues() { + initNonNull(); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + entity.setEagerProperty( "eager_update" ); + entity.setLazyProperty1( "lazy1_update" ); + } ); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + assertEquals( "eager_update", entity.getEagerProperty() ); + assertEquals( "lazy1_update", entity.getLazyProperty1() ); + + assertEquals( "lazy2_initial", entity.getLazyProperty2() ); + } ); + } + + @Test + public void updateOneEagerPropertyAndOneLazyProperty_nonNullToNonNull_sameValues() { + initNonNull(); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + entity.setEagerProperty( entity.getEagerProperty() ); + entity.setLazyProperty1( entity.getLazyProperty1() ); + } ); + + // We should not update entities when property values did not change + statementInspector().assertNoUpdate(); + } + + @Test + public void updateOneEagerPropertyAndOneLazyProperty_nonNullToNull() { + initNonNull(); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + entity.setEagerProperty( null ); + entity.setLazyProperty1( null ); + } ); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + assertNull( entity.getEagerProperty() ); + assertNull( entity.getLazyProperty1() ); + + assertEquals( "lazy2_initial", entity.getLazyProperty2() ); + } ); + } + + @Test + public void updateAllLazyProperties_nullToNull() { + initNull(); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + entity.setLazyProperty1( null ); + entity.setLazyProperty2( null ); + } ); + + // When a lazy property is modified Hibernate does not perform any select + // but during flush an update is performed + statementInspector().assertUpdate(); + } + + @Test + public void updateAllLazyProperties_nullToNonNull() { + initNull(); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + entity.setLazyProperty1( "lazy1_update" ); + entity.setLazyProperty2( "lazy2_update" ); + } ); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + assertEquals( "lazy1_update", entity.getLazyProperty1() ); + assertEquals( "lazy2_update", entity.getLazyProperty2() ); + + assertNull( entity.getEagerProperty() ); + } ); + } + + @Test + public void updateAllLazyProperties_nonNullToNonNull_differentValues() { + initNonNull(); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + entity.setLazyProperty1( "lazy1_update" ); + entity.setLazyProperty2( "lazy2_update" ); + } ); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + assertEquals( "lazy1_update", entity.getLazyProperty1() ); + assertEquals( "lazy2_update", entity.getLazyProperty2() ); + + assertEquals( "eager_initial", entity.getEagerProperty() ); + } ); + } + + @Test + public void updateAllLazyProperties_nonNullToNonNull_sameValues() { + initNonNull(); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + entity.setLazyProperty1( entity.getLazyProperty1() ); + entity.setLazyProperty2( entity.getLazyProperty2() ); + } ); + + // We should not update entities when property values did not change + statementInspector().assertNoUpdate(); + } + + @Test + public void updateAllLazyProperties_nonNullToNull() { + initNonNull(); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + entity.setLazyProperty1( null ); + entity.setLazyProperty2( null ); + } ); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + assertNull( entity.getLazyProperty1() ); + assertNull( entity.getLazyProperty2() ); + + assertEquals( "eager_initial", entity.getEagerProperty() ); + } ); + } + + @Entity + @Table(name = "LAZY_ENTITY") + private static class LazyEntity { + @Id + @GeneratedValue + Long id; + // We need at least one eager property to avoid a different problem. + @Basic + String eagerProperty; + @Basic(fetch = FetchType.LAZY) + String lazyProperty1; + // We need multiple lazy properties to reproduce the problem. + @Basic(fetch = FetchType.LAZY) + String lazyProperty2; + + Long getId() { + return id; + } + + void setId(Long id) { + this.id = id; + } + + public String getEagerProperty() { + return eagerProperty; + } + + public void setEagerProperty(String eagerProperty) { + this.eagerProperty = eagerProperty; + } + + public String getLazyProperty1() { + return lazyProperty1; + } + + public void setLazyProperty1(String lazyProperty1) { + this.lazyProperty1 = lazyProperty1; + } + + public String getLazyProperty2() { + return lazyProperty2; + } + + public void setLazyProperty2(String lazyProperty2) { + this.lazyProperty2 = lazyProperty2; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/basic/LazyBasicFieldAccessTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/basic/LazyBasicFieldAccessTest.java index 4d07652d8c5d..87d61635e9e3 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/basic/LazyBasicFieldAccessTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/basic/LazyBasicFieldAccessTest.java @@ -9,6 +9,7 @@ import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.Configuration; +import org.hibernate.testing.TestForIssue; import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.Assert; @@ -16,6 +17,8 @@ import org.junit.Test; import org.junit.runner.RunWith; +import javax.persistence.Access; +import javax.persistence.AccessType; import javax.persistence.Basic; import javax.persistence.Entity; import javax.persistence.FetchType; @@ -36,6 +39,7 @@ public class LazyBasicFieldAccessTest extends BaseCoreFunctionalTestCase { private LazyEntity entity; + private Long entityId; @Override @@ -53,82 +57,98 @@ protected void configure(Configuration configuration) { public void prepare() { doInHibernate( this::sessionFactory, s -> { LazyEntity entity = new LazyEntity(); - entity.setDescription( "desc" ); + entity.description = "desc"; s.persist( entity ); entityId = entity.id; } ); } @Test - public void test() { + public void testAttachedUpdate() { + doInHibernate( this::sessionFactory, s -> { + entity = s.get( LazyEntity.class, entityId ); + + Assert.assertFalse( isPropertyInitialized( entity, "description" ) ); + checkDirtyTracking( entity ); + + assertEquals( "desc", entity.description ); + assertTrue( isPropertyInitialized( entity, "description" ) ); + } ); + + doInHibernate( this::sessionFactory, s -> { + entity = s.get( LazyEntity.class, entityId ); + Assert.assertFalse( isPropertyInitialized( entity, "description" ) ); + entity.description = "desc1"; + + checkDirtyTracking( entity, "description" ); + + assertEquals( "desc1", entity.description ); + assertTrue( isPropertyInitialized( entity, "description" ) ); + } ); + + doInHibernate( this::sessionFactory, s -> { + entity = s.get( LazyEntity.class, entityId ); + assertEquals( "desc1", entity.description ); + } ); + } + + @Test + @TestForIssue(jiraKey = "HHH-11882") + public void testDetachedUpdate() { doInHibernate( this::sessionFactory, s -> { entity = s.get( LazyEntity.class, entityId ); Assert.assertFalse( isPropertyInitialized( entity, "description" ) ); checkDirtyTracking( entity ); - assertEquals( "desc", entity.getDescription() ); + assertEquals( "desc", entity.description ); assertTrue( isPropertyInitialized( entity, "description" ) ); } ); doInHibernate( this::sessionFactory, s -> { - entity.setDescription( "desc1" ); + entity.description = "desc1"; s.update( entity ); - //Assert.assertFalse( Hibernate.isPropertyInitialized( entity, "description" ) ); checkDirtyTracking( entity, "description" ); - assertEquals( "desc1", entity.getDescription() ); + assertEquals( "desc1", entity.description ); assertTrue( isPropertyInitialized( entity, "description" ) ); } ); doInHibernate( this::sessionFactory, s -> { entity = s.get( LazyEntity.class, entityId ); - assertEquals( "desc1", entity.getDescription() ); + assertEquals( "desc1", entity.description ); } ); doInHibernate( this::sessionFactory, s -> { - entity.setDescription( "desc2" ); + entity.description = "desc2"; LazyEntity mergedEntity = (LazyEntity) s.merge( entity ); - // Assert.assertFalse( isPropertyInitialized( entity, "description" ) ); + //Assert.assertFalse( Hibernate.isPropertyInitialized( entity, "description" ) ); checkDirtyTracking( mergedEntity, "description" ); - assertEquals( "desc2", mergedEntity.getDescription() ); + assertEquals( "desc2", mergedEntity.description ); assertTrue( isPropertyInitialized( mergedEntity, "description" ) ); } ); doInHibernate( this::sessionFactory, s -> { - LazyEntity entity = s.get( LazyEntity.class, entityId ); - assertEquals( "desc2", entity.getDescription() ); + entity = s.get( LazyEntity.class, entityId ); + assertEquals( "desc2", entity.description ); } ); } // --- // @Entity - @Table( name = "LAZY_FIELD_ENTITY" ) + @Access( AccessType.FIELD ) + @Table( name = "LAZY_PROPERTY_ENTITY" ) private static class LazyEntity { - Long id; - String description; @Id @GeneratedValue - Long getId() { - return id; - } - - void setId(Long id) { - this.id = id; - } + Long id; @Basic( fetch = FetchType.LAZY ) - String getDescription() { - return description; - } - - void setDescription(String description) { - this.description = description; - } + String description; } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/basic/LazyBasicPropertyAccessTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/basic/LazyBasicPropertyAccessTest.java index a41949fca951..46ad1e2f9bbe 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/basic/LazyBasicPropertyAccessTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/basic/LazyBasicPropertyAccessTest.java @@ -9,6 +9,7 @@ import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.Configuration; +import org.hibernate.testing.TestForIssue; import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.Assert; @@ -16,8 +17,6 @@ import org.junit.Test; import org.junit.runner.RunWith; -import javax.persistence.Access; -import javax.persistence.AccessType; import javax.persistence.Basic; import javax.persistence.Entity; import javax.persistence.FetchType; @@ -38,7 +37,6 @@ public class LazyBasicPropertyAccessTest extends BaseCoreFunctionalTestCase { private LazyEntity entity; - private Long entityId; @Override @@ -56,69 +54,111 @@ protected void configure(Configuration configuration) { public void prepare() { doInHibernate( this::sessionFactory, s -> { LazyEntity entity = new LazyEntity(); - entity.description = "desc"; + entity.setDescription( "desc" ); s.persist( entity ); - entityId = entity.id; + entityId = entity.getId(); + } ); + } + + @Test + public void testAttachedUpdate() { + doInHibernate( this::sessionFactory, s -> { + entity = s.get( LazyEntity.class, entityId ); + + Assert.assertFalse( isPropertyInitialized( entity, "description" ) ); + checkDirtyTracking( entity ); + + assertEquals( "desc", entity.getDescription() ); + assertTrue( isPropertyInitialized( entity, "description" ) ); + } ); + + doInHibernate( this::sessionFactory, s -> { + entity = s.get( LazyEntity.class, entityId ); + Assert.assertFalse( isPropertyInitialized( entity, "description" ) ); + entity.setDescription( "desc1" ); + + checkDirtyTracking( entity, "description" ); + + assertEquals( "desc1", entity.getDescription() ); + assertTrue( isPropertyInitialized( entity, "description" ) ); + } ); + + doInHibernate( this::sessionFactory, s -> { + entity = s.get( LazyEntity.class, entityId ); + assertEquals( "desc1", entity.getDescription() ); } ); } @Test - public void execute() { + @TestForIssue(jiraKey = "HHH-11882") + public void testDetachedUpdate() { doInHibernate( this::sessionFactory, s -> { entity = s.get( LazyEntity.class, entityId ); Assert.assertFalse( isPropertyInitialized( entity, "description" ) ); checkDirtyTracking( entity ); - assertEquals( "desc", entity.description ); + assertEquals( "desc", entity.getDescription() ); assertTrue( isPropertyInitialized( entity, "description" ) ); } ); doInHibernate( this::sessionFactory, s -> { - entity.description = "desc1"; + entity.setDescription( "desc1" ); s.update( entity ); - // Assert.assertFalse( Hibernate.isPropertyInitialized( entity, "description" ) ); checkDirtyTracking( entity, "description" ); - assertEquals( "desc1", entity.description ); + assertEquals( "desc1", entity.getDescription() ); assertTrue( isPropertyInitialized( entity, "description" ) ); } ); doInHibernate( this::sessionFactory, s -> { entity = s.get( LazyEntity.class, entityId ); - assertEquals( "desc1", entity.description ); + assertEquals( "desc1", entity.getDescription() ); } ); doInHibernate( this::sessionFactory, s -> { - entity.description = "desc2"; + entity.setDescription( "desc2" ); LazyEntity mergedEntity = (LazyEntity) s.merge( entity ); - //Assert.assertFalse( Hibernate.isPropertyInitialized( entity, "description" ) ); + // Assert.assertFalse( isPropertyInitialized( entity, "description" ) ); checkDirtyTracking( mergedEntity, "description" ); - assertEquals( "desc2", mergedEntity.description ); + assertEquals( "desc2", mergedEntity.getDescription() ); assertTrue( isPropertyInitialized( mergedEntity, "description" ) ); } ); doInHibernate( this::sessionFactory, s -> { - LazyEntity entity = s.get( LazyEntity.class, entityId ); - assertEquals( "desc2", entity.description ); + entity = s.get( LazyEntity.class, entityId ); + assertEquals( "desc2", entity.getDescription() ); } ); } // --- // @Entity - @Access( AccessType.FIELD ) - @Table( name = "LAZY_PROPERTY_ENTITY" ) + @Table( name = "LAZY_FIELD_ENTITY" ) private static class LazyEntity { + Long id; + String description; @Id @GeneratedValue - Long id; + Long getId() { + return id; + } + + void setId(Long id) { + this.id = id; + } @Basic( fetch = FetchType.LAZY ) - String description; + String getDescription() { + return description; + } + + void setDescription(String description) { + this.description = description; + } } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/basic/OnlyEagerBasicUpdateTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/basic/OnlyEagerBasicUpdateTest.java new file mode 100644 index 000000000000..16ed6f90b1a4 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/basic/OnlyEagerBasicUpdateTest.java @@ -0,0 +1,259 @@ +/* + * 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.basic; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.orm.test.bytecode.enhancement.lazy.NoDirtyCheckingContext; + +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.jdbc.SQLStatementInspector; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import javax.persistence.Basic; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Table; + +/** + * Tests the behavior of basic property updates when all properties are eager (the default). + *

      + * This is mostly for comparison with {@link EagerAndLazyBasicUpdateTest}/{@link OnlyLazyBasicUpdateTest}, + * because the mere presence of lazy properties in one entity may affect the behavior of eager properties, too. + */ +@RunWith(BytecodeEnhancerRunner.class) +@CustomEnhancementContext({ EnhancerTestContext.class, NoDirtyCheckingContext.class }) +@TestForIssue(jiraKey = "HHH-16049") +public class OnlyEagerBasicUpdateTest extends BaseCoreFunctionalTestCase { + + private Long entityId; + + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { EagerEntity.class }; + } + + @Override + protected void prepareBasicRegistryBuilder(StandardServiceRegistryBuilder serviceRegistryBuilder) { + super.prepareBasicRegistryBuilder( serviceRegistryBuilder ); + serviceRegistryBuilder.applySetting( AvailableSettings.STATEMENT_INSPECTOR, SQLStatementInspector.class.getName() ); + } + + SQLStatementInspector statementInspector() { + return (SQLStatementInspector) sessionFactory().getSessionFactoryOptions().getStatementInspector(); + } + + private void initNull() { + doInHibernate( this::sessionFactory, s -> { + EagerEntity entity = new EagerEntity(); + s.persist( entity ); + entityId = entity.getId(); + } ); + } + + private void initNonNull() { + doInHibernate( this::sessionFactory, s -> { + EagerEntity entity = new EagerEntity(); + entity.setEagerProperty1( "eager1_initial" ); + entity.setEagerProperty2( "eager2_initial" ); + s.persist( entity ); + entityId = entity.getId(); + } ); + } + + @Before + public void clearStatementInspector() { + statementInspector().clear(); + } + + @Test + public void updateSomeEagerProperty_nullToNull() { + initNull(); + doInHibernate( this::sessionFactory, s -> { + EagerEntity entity = s.get( EagerEntity.class, entityId ); + entity.setEagerProperty1( null ); + } ); + + // We should not update entities when property values did not change + statementInspector().assertNoUpdate(); + } + + @Test + public void updateSomeEagerProperty_nullToNonNull() { + initNull(); + doInHibernate( this::sessionFactory, s -> { + EagerEntity entity = s.get( EagerEntity.class, entityId ); + entity.setEagerProperty1( "eager1_update" ); + } ); + doInHibernate( this::sessionFactory, s -> { + EagerEntity entity = s.get( EagerEntity.class, entityId ); + assertEquals( "eager1_update", entity.getEagerProperty1() ); + + assertNull( entity.getEagerProperty2() ); + } ); + } + + @Test + public void updateSomeEagerProperty_nonNullToNonNull_differentValues() { + initNonNull(); + doInHibernate( this::sessionFactory, s -> { + EagerEntity entity = s.get( EagerEntity.class, entityId ); + entity.setEagerProperty1( "eager1_update" ); + } ); + doInHibernate( this::sessionFactory, s -> { + EagerEntity entity = s.get( EagerEntity.class, entityId ); + assertEquals( "eager1_update", entity.getEagerProperty1() ); + + assertEquals( "eager2_initial", entity.getEagerProperty2() ); + } ); + } + + @Test + public void updateSomeEagerProperty_nonNullToNonNull_sameValues() { + initNonNull(); + doInHibernate( this::sessionFactory, s -> { + EagerEntity entity = s.get( EagerEntity.class, entityId ); + entity.setEagerProperty1( entity.getEagerProperty1() ); + } ); + + // We should not update entities when property values did not change + statementInspector().assertNoUpdate(); + } + + @Test + public void updateSomeEagerProperty_nonNullToNull() { + initNonNull(); + doInHibernate( this::sessionFactory, s -> { + EagerEntity entity = s.get( EagerEntity.class, entityId ); + entity.setEagerProperty1( null ); + } ); + doInHibernate( this::sessionFactory, s -> { + EagerEntity entity = s.get( EagerEntity.class, entityId ); + assertNull( entity.getEagerProperty1() ); + + assertEquals( "eager2_initial", entity.getEagerProperty2() ); + } ); + } + + @Test + public void updateAllEagerProperties_nullToNull() { + initNull(); + doInHibernate( this::sessionFactory, s -> { + EagerEntity entity = s.get( EagerEntity.class, entityId ); + entity.setEagerProperty1( null ); + entity.setEagerProperty2( null ); + } ); + + // We should not update entities when property values did not change + statementInspector().assertNoUpdate(); + } + + @Test + public void updateAllEagerProperties_nullToNonNull() { + initNull(); + doInHibernate( this::sessionFactory, s -> { + EagerEntity entity = s.get( EagerEntity.class, entityId ); + entity.setEagerProperty1( "eager1_update" ); + entity.setEagerProperty2( "eager2_update" ); + } ); + doInHibernate( this::sessionFactory, s -> { + EagerEntity entity = s.get( EagerEntity.class, entityId ); + assertEquals( "eager1_update", entity.getEagerProperty1() ); + assertEquals( "eager2_update", entity.getEagerProperty2() ); + } ); + } + + @Test + public void updateAllEagerProperties_nonNullToNonNull_differentValues() { + initNonNull(); + doInHibernate( this::sessionFactory, s -> { + EagerEntity entity = s.get( EagerEntity.class, entityId ); + entity.setEagerProperty1( "eager1_update" ); + entity.setEagerProperty2( "eager2_update" ); + } ); + doInHibernate( this::sessionFactory, s -> { + EagerEntity entity = s.get( EagerEntity.class, entityId ); + assertEquals( "eager1_update", entity.getEagerProperty1() ); + assertEquals( "eager2_update", entity.getEagerProperty2() ); + } ); + } + + @Test + public void updateAllEagerProperties_nonNullToNonNull_sameValues() { + initNonNull(); + doInHibernate( this::sessionFactory, s -> { + EagerEntity entity = s.get( EagerEntity.class, entityId ); + entity.setEagerProperty1( entity.getEagerProperty1() ); + entity.setEagerProperty2( entity.getEagerProperty2() ); + } ); + + // We should not update entities when property values did not change + statementInspector().assertNoUpdate(); + } + + @Test + public void updateAllEagerProperties_nonNullToNull() { + initNonNull(); + doInHibernate( this::sessionFactory, s -> { + EagerEntity entity = s.get( EagerEntity.class, entityId ); + entity.setEagerProperty1( null ); + entity.setEagerProperty2( null ); + } ); + doInHibernate( this::sessionFactory, s -> { + EagerEntity entity = s.get( EagerEntity.class, entityId ); + assertNull( entity.getEagerProperty1() ); + assertNull( entity.getEagerProperty2() ); + } ); + } + + @Entity + @Table(name = "LAZY_ENTITY") + private static class EagerEntity { + @Id + @GeneratedValue + Long id; + @Basic + String eagerProperty1; + @Basic + String eagerProperty2; + + Long getId() { + return id; + } + + void setId(Long id) { + this.id = id; + } + + public String getEagerProperty1() { + return eagerProperty1; + } + + public void setEagerProperty1(String eagerProperty1) { + this.eagerProperty1 = eagerProperty1; + } + + public String getEagerProperty2() { + return eagerProperty2; + } + + public void setEagerProperty2(String eagerProperty2) { + this.eagerProperty2 = eagerProperty2; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/basic/OnlyLazyBasicUpdateTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/basic/OnlyLazyBasicUpdateTest.java new file mode 100644 index 000000000000..da8a7a46f274 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/basic/OnlyLazyBasicUpdateTest.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.test.bytecode.enhancement.lazy.basic; + +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.orm.test.bytecode.enhancement.lazy.NoDirtyCheckingContext; + +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.jdbc.SQLStatementInspector; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import javax.persistence.Basic; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Table; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +@RunWith(BytecodeEnhancerRunner.class) +@CustomEnhancementContext({ EnhancerTestContext.class, NoDirtyCheckingContext.class }) +@TestForIssue(jiraKey = { "HHH-15634", "HHH-16049" }) +public class OnlyLazyBasicUpdateTest extends BaseCoreFunctionalTestCase { + + private Long entityId; + + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { LazyEntity.class }; + } + + @Override + protected void prepareBasicRegistryBuilder(StandardServiceRegistryBuilder serviceRegistryBuilder) { + super.prepareBasicRegistryBuilder( serviceRegistryBuilder ); + serviceRegistryBuilder.applySetting( AvailableSettings.STATEMENT_INSPECTOR, SQLStatementInspector.class.getName() ); + } + + SQLStatementInspector statementInspector() { + return (SQLStatementInspector) sessionFactory().getSessionFactoryOptions().getStatementInspector(); + } + + private void initNull() { + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = new LazyEntity(); + s.persist( entity ); + entityId = entity.getId(); + } ); + } + + private void initNonNull() { + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = new LazyEntity(); + entity.setLazyProperty1( "lazy1_initial" ); + entity.setLazyProperty2( "lazy2_initial" ); + s.persist( entity ); + entityId = entity.getId(); + } ); + } + + @Before + public void clearStatementInspector() { + statementInspector().clear(); + } + + @Test + public void updateSomeLazyProperty_nullToNull() { + initNull(); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + entity.setLazyProperty1( null ); + } ); + + // When a lazy property is modified Hibernate does not perform any select + // but during flush an update is performed + statementInspector().assertUpdate(); + } + + @Test + public void updateSomeLazyProperty_nullToNonNull() { + initNull(); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + entity.setLazyProperty1( "lazy1_update" ); + } ); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + assertEquals( "lazy1_update", entity.getLazyProperty1() ); + + assertNull( entity.getLazyProperty2() ); + } ); + } + + @Test + public void updateSomeLazyProperty_nonNullToNonNull_differentValues() { + initNonNull(); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + entity.setLazyProperty1( "lazy1_update" ); + } ); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + assertEquals( "lazy1_update", entity.getLazyProperty1() ); + + assertEquals( "lazy2_initial", entity.getLazyProperty2() ); + } ); + } + + @Test + public void updateSomeLazyProperty_nonNullToNonNull_sameValues() { + initNonNull(); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + entity.setLazyProperty1( entity.getLazyProperty1() ); + } ); + + // We should not update entities when property values did not change + statementInspector().assertNoUpdate(); + } + + @Test + public void updateSomeLazyProperty_nonNullToNull() { + initNonNull(); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + entity.setLazyProperty1( null ); + } ); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + assertNull( entity.getLazyProperty1() ); + + assertEquals( "lazy2_initial", entity.getLazyProperty2() ); + } ); + } + + @Test + public void updateAllLazyProperties_nullToNull() { + initNull(); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + entity.setLazyProperty1( null ); + entity.setLazyProperty2( null ); + } ); + + // When a lazy property is modified Hibernate does not perform any select + // but during flush an update is performed + statementInspector().assertUpdate(); + } + + @Test + public void updateAllLazyProperties_nullToNonNull() { + initNull(); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + entity.setLazyProperty1( "lazy1_update" ); + entity.setLazyProperty2( "lazy2_update" ); + } ); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + assertEquals( "lazy1_update", entity.getLazyProperty1() ); + assertEquals( "lazy2_update", entity.getLazyProperty2() ); + } ); + } + + @Test + public void updateAllLazyProperties_nonNullToNonNull_differentValues() { + initNonNull(); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + entity.setLazyProperty1( "lazy1_update" ); + entity.setLazyProperty2( "lazy2_update" ); + } ); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + assertEquals( "lazy1_update", entity.getLazyProperty1() ); + assertEquals( "lazy2_update", entity.getLazyProperty2() ); + } ); + } + + @Test + public void updateAllLazyProperties_nonNullToNonNull_sameValues() { + initNonNull(); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + entity.setLazyProperty1( entity.getLazyProperty1() ); + entity.setLazyProperty2( entity.getLazyProperty2() ); + } ); + + // We should not update entities when property values did not change + statementInspector().assertNoUpdate(); + } + + @Test + public void updateAllLazyProperties_nonNullToNull() { + initNonNull(); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + entity.setLazyProperty1( null ); + entity.setLazyProperty2( null ); + } ); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + assertNull( entity.getLazyProperty1() ); + assertNull( entity.getLazyProperty2() ); + } ); + } + + @Entity + @Table(name = "LAZY_ENTITY") + private static class LazyEntity { + @Id + @GeneratedValue + Long id; + // ALL properties must be lazy in order to reproduce the problem. + @Basic(fetch = FetchType.LAZY) + String lazyProperty1; + @Basic(fetch = FetchType.LAZY) + String lazyProperty2; + + Long getId() { + return id; + } + + void setId(Long id) { + this.id = id; + } + + public String getLazyProperty1() { + return lazyProperty1; + } + + public void setLazyProperty1(String lazyProperty1) { + this.lazyProperty1 = lazyProperty1; + } + + public String getLazyProperty2() { + return lazyProperty2; + } + + public void setLazyProperty2(String lazyProperty2) { + this.lazyProperty2 = lazyProperty2; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/MultiLazyBasicInLazyGroupUpdateTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/MultiLazyBasicInLazyGroupUpdateTest.java new file mode 100644 index 000000000000..d782bbeacf2b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/MultiLazyBasicInLazyGroupUpdateTest.java @@ -0,0 +1,185 @@ +/* + * 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 static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import org.hibernate.annotations.LazyGroup; +import org.hibernate.orm.test.bytecode.enhancement.lazy.NoDirtyCheckingContext; + +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.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import javax.persistence.Basic; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Table; + +@RunWith(BytecodeEnhancerRunner.class) +@CustomEnhancementContext( { EnhancerTestContext.class, NoDirtyCheckingContext.class} ) +public class MultiLazyBasicInLazyGroupUpdateTest extends BaseCoreFunctionalTestCase { + + private Long entityId; + + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { LazyEntity.class }; + } + + @Before + public void prepare() { + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = new LazyEntity(); + s.persist( entity ); + entityId = entity.getId(); + } ); + } + + @Test + public void updateOneLazyProperty() { + // null -> non-null + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + entity.setLazyProperty1( "update1" ); + } ); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + assertEquals( "update1", entity.getLazyProperty1() ); + assertNull(entity.getLazyProperty2()); + assertNull(entity.getEagerProperty()); + } ); + + // non-null -> non-null + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + entity.setLazyProperty1( "update2" ); + } ); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + assertEquals( "update2", entity.getLazyProperty1() ); + assertNull(entity.getLazyProperty2()); + assertNull(entity.getEagerProperty()); + } ); + } + + @Test + public void updateOneEagerPropertyAndOneLazyProperty() { + // null -> non-null + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + entity.setEagerProperty( "eager_update1" ); + entity.setLazyProperty1( "update1" ); + } ); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + assertEquals( "eager_update1", entity.getEagerProperty() ); + assertEquals( "update1", entity.getLazyProperty1() ); + assertNull(entity.getLazyProperty2()); + } ); + + // non-null -> non-null + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + entity.setEagerProperty( "eager_update2" ); + entity.setLazyProperty1( "update2" ); + } ); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + assertEquals( "eager_update2", entity.getEagerProperty() ); + assertEquals( "update2", entity.getLazyProperty1() ); + assertNull(entity.getLazyProperty2()); + } ); + } + + @Test + public void updateAllLazyProperties() { + // null -> non-null + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + entity.setLazyProperty1( "update1" ); + entity.setLazyProperty2( "update2_1" ); + } ); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + assertEquals( "update1", entity.getLazyProperty1() ); + assertEquals( "update2_1", entity.getLazyProperty2() ); + assertNull( entity.getEagerProperty() ); + } ); + + // non-null -> non-null + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + entity.setLazyProperty1( "update2" ); + entity.setLazyProperty2( "update2_2" ); + } ); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + assertEquals( "update2", entity.getLazyProperty1() ); + assertEquals( "update2_2", entity.getLazyProperty2() ); + assertNull( entity.getEagerProperty() ); + } ); + } + + @Entity + @Table(name = "LAZY_ENTITY") + private static class LazyEntity { + @Id + @GeneratedValue + Long id; + // We need at least one eager property to avoid a different problem. + @Basic + String eagerProperty; + @Basic(fetch = FetchType.LAZY) + @LazyGroup("group1") + String lazyProperty1; + // We need multiple lazy properties to reproduce the problem. + @Basic(fetch = FetchType.LAZY) + @LazyGroup("group2") + String lazyProperty2; + + Long getId() { + return id; + } + + void setId(Long id) { + this.id = id; + } + + public String getEagerProperty() { + return eagerProperty; + } + + public void setEagerProperty(String eagerProperty) { + this.eagerProperty = eagerProperty; + } + + public String getLazyProperty1() { + return lazyProperty1; + } + + public void setLazyProperty1(String lazyProperty1) { + this.lazyProperty1 = lazyProperty1; + } + + public String getLazyProperty2() { + return lazyProperty2; + } + + public void setLazyProperty2(String lazyProperty2) { + this.lazyProperty2 = lazyProperty2; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/MultiLazyBasicInLazyGroupUpdateToNullTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/MultiLazyBasicInLazyGroupUpdateToNullTest.java new file mode 100644 index 000000000000..a3cbd446a338 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/MultiLazyBasicInLazyGroupUpdateToNullTest.java @@ -0,0 +1,150 @@ +/* + * 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 org.hibernate.annotations.LazyGroup; +import org.hibernate.orm.test.bytecode.enhancement.lazy.NoDirtyCheckingContext; + +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.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import javax.persistence.Basic; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Table; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +@RunWith(BytecodeEnhancerRunner.class) +@CustomEnhancementContext( { EnhancerTestContext.class, NoDirtyCheckingContext.class} ) +public class MultiLazyBasicInLazyGroupUpdateToNullTest extends BaseCoreFunctionalTestCase { + + private Long entityId; + + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { LazyEntity.class }; + } + + @Before + public void prepare() { + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = new LazyEntity(); + entity.setEagerProperty( "eager" ); + entity.setLazyProperty1( "update1" ); + entity.setLazyProperty2( "update2" ); + s.persist( entity ); + entityId = entity.getId(); + } ); + } + + @Test + public void updateOneLazyProperty() { + // non-null -> null + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + entity.setLazyProperty1( null ); + } ); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + assertNull( entity.getLazyProperty1() ); + assertNotNull( entity.getLazyProperty2() ); + assertNotNull( entity.getEagerProperty() ); + } ); + } + + @Test + public void updateOneEagerPropertyAndOneLazyProperty() { + // non-null -> null + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + entity.setEagerProperty( null ); + entity.setLazyProperty1( null ); + } ); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + assertNull( entity.getEagerProperty() ); + assertNull( entity.getLazyProperty1() ); + assertNotNull( entity.getLazyProperty2() ); + } ); + } + + @Test + public void updateAllLazyProperties() { + // non-null -> null + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + entity.setLazyProperty1( null ); + entity.setLazyProperty2( null ); + } ); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + assertNull( entity.getLazyProperty1() ); + assertNull( entity.getLazyProperty2() ); + assertNotNull( entity.getEagerProperty() ); + } ); + } + + @Entity + @Table(name = "LAZY_ENTITY") + private static class LazyEntity { + @Id + @GeneratedValue + Long id; + // We need at least one eager property to avoid a different problem. + @Basic + String eagerProperty; + @Basic(fetch = FetchType.LAZY) + @LazyGroup("group1") + String lazyProperty1; + // We need multiple lazy properties to reproduce the problem. + @Basic(fetch = FetchType.LAZY) + @LazyGroup("group2") + String lazyProperty2; + + Long getId() { + return id; + } + + void setId(Long id) { + this.id = id; + } + + public String getEagerProperty() { + return eagerProperty; + } + + public void setEagerProperty(String eagerProperty) { + this.eagerProperty = eagerProperty; + } + + public String getLazyProperty1() { + return lazyProperty1; + } + + public void setLazyProperty1(String lazyProperty1) { + this.lazyProperty1 = lazyProperty1; + } + + public String getLazyProperty2() { + return lazyProperty2; + } + + public void setLazyProperty2(String lazyProperty2) { + this.lazyProperty2 = lazyProperty2; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/OnlyLazyBasicInLazyGroupBasicUpdateTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/OnlyLazyBasicInLazyGroupBasicUpdateTest.java new file mode 100644 index 000000000000..45b3da4a4a2c --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/OnlyLazyBasicInLazyGroupBasicUpdateTest.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.test.bytecode.enhancement.lazy.group; + +import org.hibernate.annotations.LazyGroup; +import org.hibernate.orm.test.bytecode.enhancement.lazy.NoDirtyCheckingContext; + +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.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import javax.persistence.Basic; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Table; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +@RunWith(BytecodeEnhancerRunner.class) +@CustomEnhancementContext({ EnhancerTestContext.class, NoDirtyCheckingContext.class }) +public class OnlyLazyBasicInLazyGroupBasicUpdateTest extends BaseCoreFunctionalTestCase { + + private Long entityId; + + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { LazyEntity.class }; + } + + @Before + public void prepare() { + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = new LazyEntity(); + s.persist( entity ); + entityId = entity.getId(); + } ); + } + + @Test + public void updateOneLazyProperty() { + // null -> non-null + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + entity.setLazyProperty1( "update1" ); + } ); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + assertEquals( "update1", entity.getLazyProperty1() ); + assertNull( entity.getLazyProperty2() ); + } ); + + // non-null -> non-null + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + entity.setLazyProperty1( "update2" ); + } ); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + assertEquals( "update2", entity.getLazyProperty1() ); + assertNull( entity.getLazyProperty2() ); + } ); + } + + @Test + public void updateAllLazyProperties() { + // null -> non-null + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + entity.setLazyProperty1( "update1" ); + entity.setLazyProperty2( "update2_1" ); + } ); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + assertEquals( "update1", entity.getLazyProperty1() ); + assertEquals( "update2_1", entity.getLazyProperty2() ); + } ); + + // non-null -> non-null + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + entity.setLazyProperty1( "update2" ); + entity.setLazyProperty2( "update2_2" ); + } ); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + assertEquals( "update2", entity.getLazyProperty1() ); + assertEquals( "update2_2", entity.getLazyProperty2() ); + } ); + } + + @Entity + @Table(name = "LAZY_ENTITY") + private static class LazyEntity { + @Id + @GeneratedValue + Long id; + // ALL properties must be lazy in order to reproduce the problem. + @Basic(fetch = FetchType.LAZY) + @LazyGroup("group1") + String lazyProperty1; + @Basic(fetch = FetchType.LAZY) + @LazyGroup("group2") + String lazyProperty2; + + Long getId() { + return id; + } + + void setId(Long id) { + this.id = id; + } + + public String getLazyProperty1() { + return lazyProperty1; + } + + public void setLazyProperty1(String lazyProperty1) { + this.lazyProperty1 = lazyProperty1; + } + + public String getLazyProperty2() { + return lazyProperty2; + } + + public void setLazyProperty2(String lazyProperty2) { + this.lazyProperty2 = lazyProperty2; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/OnlyLazyBasicInLazyGroupBasicUpdateToNullTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/OnlyLazyBasicInLazyGroupBasicUpdateToNullTest.java new file mode 100644 index 000000000000..680ddd861801 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/OnlyLazyBasicInLazyGroupBasicUpdateToNullTest.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.test.bytecode.enhancement.lazy.group; + +import org.hibernate.annotations.LazyGroup; +import org.hibernate.orm.test.bytecode.enhancement.lazy.NoDirtyCheckingContext; + +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.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import javax.persistence.Basic; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Table; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +@RunWith(BytecodeEnhancerRunner.class) +@CustomEnhancementContext( { EnhancerTestContext.class, NoDirtyCheckingContext.class} ) +public class OnlyLazyBasicInLazyGroupBasicUpdateToNullTest extends BaseCoreFunctionalTestCase { + + private Long entityId; + + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { LazyEntity.class }; + } + + @Before + public void prepare() { + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = new LazyEntity(); + entity.setLazyProperty1( "update1" ); + entity.setLazyProperty2( "update2" ); + s.persist( entity ); + entityId = entity.getId(); + } ); + } + + @Test + public void updateOneLazyProperty() { + // non-null -> null + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + entity.setLazyProperty1( null ); + } ); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + assertNull( entity.getLazyProperty1() ); + assertNotNull( entity.getLazyProperty2() ); + } ); + } + + @Test + public void updateAllLazyProperties() { + // non-null -> null + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + entity.setLazyProperty1( null ); + entity.setLazyProperty2( null ); + } ); + doInHibernate( this::sessionFactory, s -> { + LazyEntity entity = s.get( LazyEntity.class, entityId ); + assertNull( entity.getLazyProperty1() ); + assertNull( entity.getLazyProperty2() ); + } ); + } + + @Entity + @Table(name = "LAZY_ENTITY") + private static class LazyEntity { + @Id + @GeneratedValue + Long id; + // ALL properties must be lazy in order to reproduce the problem. + @Basic(fetch = FetchType.LAZY) + @LazyGroup("group1") + String lazyProperty1; + @Basic(fetch = FetchType.LAZY) + @LazyGroup("group2") + String lazyProperty2; + + Long getId() { + return id; + } + + void setId(Long id) { + this.id = id; + } + + public String getLazyProperty1() { + return lazyProperty1; + } + + public void setLazyProperty1(String lazyProperty1) { + this.lazyProperty1 = lazyProperty1; + } + + public String getLazyProperty2() { + return lazyProperty2; + } + + public void setLazyProperty2(String lazyProperty2) { + this.lazyProperty2 = lazyProperty2; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/notfound/LazyNotFoundManyToOneNonUpdatableNonInsertableTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/notfound/LazyNotFoundManyToOneNonUpdatableNonInsertableTest.java index 3b22a0598efb..f516b7ac69ea 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/notfound/LazyNotFoundManyToOneNonUpdatableNonInsertableTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/notfound/LazyNotFoundManyToOneNonUpdatableNonInsertableTest.java @@ -28,9 +28,8 @@ import org.junit.Test; import org.junit.runner.RunWith; +import static org.assertj.core.api.Assertions.assertThat; import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; /** * @author Gail Badner @@ -70,8 +69,12 @@ public void test() { doInHibernate( this::sessionFactory, session -> { User user = session.find( User.class, ID ); - assertFalse( Hibernate.isPropertyInitialized( user, "lazy" ) ); - assertNull( user.getLazy() ); + assertThat( Hibernate.isPropertyInitialized( user, "lazy" ) ) + .describedAs( "Expecting `User#lazy to be (bytecode) initialized due to `@NotFound`" ) + .isTrue(); + assertThat( user.getLazy() ) + .describedAs( "Expecting `User#lazy to null due to `NotFoundAction#IGNORE`" ) + .isNull(); } ); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/notfound/LazyNotFoundOneToOneNonUpdatableNonInsertableTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/notfound/LazyNotFoundOneToOneNonUpdatableNonInsertableTest.java index 33673f001f22..bab77547af96 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/notfound/LazyNotFoundOneToOneNonUpdatableNonInsertableTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/notfound/LazyNotFoundOneToOneNonUpdatableNonInsertableTest.java @@ -33,6 +33,7 @@ import org.junit.Test; import org.junit.runner.RunWith; +import static org.assertj.core.api.Assertions.assertThat; import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; @@ -73,9 +74,14 @@ public void test() { doInHibernate( this::sessionFactory, session -> { User user = session.find( User.class, ID ); - assertFalse( Hibernate.isPropertyInitialized( user, "lazy" ) ); - assertNull( user.getLazy() ); - assertTrue( Hibernate.isPropertyInitialized( user, "lazy" ) ); + + assertThat( Hibernate.isPropertyInitialized( user, "lazy" ) ) + .describedAs( "Expecting `User#lazy to be (bytecode) initialized due to `@NotFound`" ) + .isTrue(); + assertThat( user.getLazy() ) + .describedAs( "Expecting `User#lazy to null due to `NotFoundAction#IGNORE`" ) + .isNull(); + } ); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/notfound/LazyNotFoundOneToOneTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/notfound/LazyNotFoundOneToOneTest.java index 7d6676a7ac0d..c439bdc28c97 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/notfound/LazyNotFoundOneToOneTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/notfound/LazyNotFoundOneToOneTest.java @@ -21,7 +21,6 @@ import org.hibernate.annotations.LazyToOneOption; import org.hibernate.annotations.NotFound; import org.hibernate.annotations.NotFoundAction; -import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.Configuration; import org.hibernate.testing.TestForIssue; @@ -31,11 +30,8 @@ import org.junit.Test; import org.junit.runner.RunWith; -import static org.hamcrest.CoreMatchers.is; +import static org.assertj.core.api.Assertions.assertThat; 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.assertThat; /** * @author Gail Badner @@ -86,10 +82,10 @@ public void test() { this::sessionFactory, session -> { User user = session.find( User.class, ID ); - assertThat( sqlInterceptor.getQueryCount(), is( 1 ) ); - assertFalse( Hibernate.isPropertyInitialized( user, "lazy" ) ); + assertThat( sqlInterceptor.getQueryCount() ).isEqualTo( 2 ); + assertThat( Hibernate.isPropertyInitialized( user, "lazy" ) ).isTrue(); - assertNull( user.getLazy() ); + assertThat( user.getLazy() ).isNull(); } ); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/version/VersionedEntityTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/version/VersionedEntityTest.java new file mode 100644 index 000000000000..bf07a9e2e387 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/version/VersionedEntityTest.java @@ -0,0 +1,241 @@ +package org.hibernate.test.bytecode.enhancement.version; + +import org.hibernate.Hibernate; +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 java.io.Serializable; +import java.util.HashSet; +import java.util.Set; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.ForeignKey; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.MappedSuperclass; +import javax.persistence.OneToMany; +import javax.persistence.Version; + +import static org.hibernate.Hibernate.isInitialized; +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +@TestForIssue(jiraKey = "HHH-15134") +@RunWith(BytecodeEnhancerRunner.class) +public class VersionedEntityTest extends BaseCoreFunctionalTestCase { + private final Long parentID = 1L; + + @Override + public Class[] getAnnotatedClasses() { + return new Class[]{ FooEntity.class, BarEntity.class, BazEntity.class }; + } + + @Before + public void prepare() { + doInJPA(this::sessionFactory, em -> { + final FooEntity entity = FooEntity.of( parentID, "foo" ); + em.persist( entity ); + }); + } + + @Test + public void testUpdateVersionedEntity() { + doInJPA(this::sessionFactory, em -> { + final FooEntity entity = em.getReference( FooEntity.class, parentID ); + + assertFalse( isInitialized( entity ) ); + assertTrue( Hibernate.isPropertyInitialized( entity, "id" ) ); + assertFalse( Hibernate.isPropertyInitialized( entity, "name" ) ); + assertFalse( Hibernate.isPropertyInitialized( entity, "version" ) ); + assertFalse( Hibernate.isPropertyInitialized( entity, "bars" ) ); + assertFalse( Hibernate.isPropertyInitialized( entity, "bazzes" ) ); + + entity.setName( "bar" ); + }); + } + + @MappedSuperclass + public static abstract class AbstractEntity { + + public abstract T getId(); + + public abstract void setId(T id); + + @Override + public int hashCode() { + return getClass().hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (obj == null || obj.getClass() != getClass()) return false; + + final AbstractEntity other = (AbstractEntity) obj; + return getId() != null && getId().equals(other.getId()); + } + } + + @Entity(name = "FooEntity") + public static class FooEntity extends AbstractEntity { + + @Id + private long id; + @Version + private int version; + + private String name; + + @OneToMany(mappedBy = "foo", cascade = CascadeType.ALL, targetEntity = BarEntity.class, orphanRemoval = true) + public Set bars = new HashSet<>(); + + @OneToMany(mappedBy = "foo", cascade = CascadeType.ALL, targetEntity = BazEntity.class, orphanRemoval = true) + public Set bazzes = new HashSet<>(); + + public static FooEntity of(long id, String name) { + final FooEntity f = new FooEntity(); + f.id = id; + f.name = name; + return f; + } + + @Override + public Long getId() { + return id; + } + + @Override + public void setId(Long id) { + this.id = id; + } + + public int getVersion() { + return version; + } + + public void setVersion(int version) { + this.version = version; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Set getBars() { + return bars; + } + + public void addBar(BarEntity bar) { + bars.add(bar); + bar.setFoo(this); + } + + public void removeBar(BarEntity bar) { + bars.remove(bar); + bar.setFoo(null); + } + + public Set getBazzes() { + return bazzes; + } + + public void addBaz(BazEntity baz) { + bazzes.add(baz); + baz.setFoo(this); + } + + public void removeBaz(BazEntity baz) { + bazzes.remove(baz); + baz.setFoo(null); + } + + @Override + public String toString() { + return String.format("FooEntity: id=%d, version=%d, name=%s", id, version, name); + } + } + + @Entity(name = "BazEntity") + public static class BazEntity extends AbstractEntity { + + @Id + @GeneratedValue + private long id; + + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(foreignKey = @ForeignKey(name = "fk_baz_foo"), nullable = false) + private FooEntity foo; + + @Override + public Long getId() { + return id; + } + + @Override + public void setId(Long id) { + this.id = id; + } + + public FooEntity getFoo() { + return foo; + } + + public void setFoo(FooEntity foo) { + this.foo = foo; + } + + @Override + public String toString() { + return String.format("BazEntity: id=%d", id); + } + } + + @Entity(name = "BarEntity") + public static class BarEntity extends AbstractEntity { + + @Id + @GeneratedValue + private long id; + + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(foreignKey = @ForeignKey(name = "fk_bar_foo"), nullable = false) + private FooEntity foo; + + @Override + public Long getId() { + return id; + } + + @Override + public void setId(Long id) { + this.id = id; + } + + public FooEntity getFoo() { + return foo; + } + + public void setFoo(FooEntity foo) { + this.foo = foo; + } + + @Override + public String toString() { + return String.format("BarEntity: id=%d", id); + } + } +} + diff --git a/hibernate-core/src/test/java/org/hibernate/test/cache/CacheModeGetUpdateTest.java b/hibernate-core/src/test/java/org/hibernate/test/cache/CacheModeGetUpdateTest.java new file mode 100644 index 000000000000..9a1b72338fc3 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/cache/CacheModeGetUpdateTest.java @@ -0,0 +1,222 @@ +/* + * 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.cache; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import javax.persistence.Cacheable; +import javax.persistence.Column; +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.CacheMode; +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; + +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Marco Belladelli + */ +public class CacheModeGetUpdateTest extends BaseCoreFunctionalTestCase { + private static final long PHONE_ID = 1L; + private static final long PERSON_ID = 2L; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Phone.class, + Person.class + }; + } + + @Before + public void setUp() { + inTransaction( session -> { + session.persist( new Phone( PHONE_ID, "123" ) ); + session.persist( new Person( PERSON_ID, "Marco" ) ); + } ); + } + + @After + public void tearDown() { + inTransaction( session -> { + session.createQuery( "delete from Phone" ).executeUpdate(); + session.createQuery( "delete from Person" ).executeUpdate(); + } ); + } + + @Test + public void test() { + inTransaction( session -> { + session.setCacheMode( CacheMode.GET ); + final Phone phone = session.get( Phone.class, PHONE_ID ); + final Person person = session.get( Person.class, PERSON_ID ); + phone.setPerson( person ); + person.getPhones().add( phone ); + session.persist( phone ); + } ); + // in a different transaction + inTransaction( session -> { + final Phone phone = session.get( Phone.class, PHONE_ID ); + assertThat( phone.getPerson() ).isNotNull(); + } ); + } + + @Entity( name = "Phone" ) + @Cacheable + @Cache( usage = CacheConcurrencyStrategy.TRANSACTIONAL ) + public static class Phone { + @Id + private Long id; + + @Column( name = "phone_number" ) + private String number; + + @ManyToOne( fetch = FetchType.LAZY ) + @JoinColumn + private Person person; + + public Phone() { + } + + public Phone(final long id, final String number) { + setId( id ); + setNumber( number ); + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getNumber() { + return number; + } + + public void setNumber(String number) { + this.number = number; + } + + public Person getPerson() { + return person; + } + + public void setPerson(Person person) { + this.person = person; + } + + @Override + public String toString() { + return "Phone{" + + "id=" + id + + ", number='" + number + '\'' + + ", person=" + person + + '}'; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + Phone phone = (Phone) o; + return Objects.equals( id, phone.id ) && Objects.equals( number, phone.number ) && Objects.equals( + person, + phone.person + ); + } + + @Override + public int hashCode() { + return Objects.hash( id, number, person ); + } + } + + @Entity( name = "Person" ) + @Cacheable + @Cache( usage = CacheConcurrencyStrategy.TRANSACTIONAL ) + public static class Person { + + @Id + private Long id; + + private String name; + + @OneToMany( fetch = FetchType.LAZY, mappedBy = "person" ) + private final Set phones = new HashSet<>(); + + public Person() { + } + + public Person(final long id, final String name) { + setId( id ); + setName( name ); + } + + public Long getId() { + return id; + } + + public void setId(final Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + @Override + public String toString() { + return "Person{" + + "id=" + id + + ", name='" + name + + '}'; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + Person person = (Person) o; + return Objects.equals( id, person.id ) && Objects.equals( name, person.name ); + } + + @Override + public int hashCode() { + return Objects.hash( id, name ); + } + + public void setName(final String name) { + this.name = name; + } + + public Set getPhones() { + return phones; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/cache/CacheRegionStatisticsTest.java b/hibernate-core/src/test/java/org/hibernate/test/cache/CacheRegionStatisticsTest.java new file mode 100644 index 000000000000..5517650797d3 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/cache/CacheRegionStatisticsTest.java @@ -0,0 +1,114 @@ +/* + * 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.cache; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.Id; + +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.stat.CacheRegionStatistics; +import org.hibernate.stat.Statistics; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.cache.CachingRegionFactory; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; + +/** + * @author Christian Beikov + */ +public class CacheRegionStatisticsTest extends BaseNonConfigCoreFunctionalTestCase { + + @Test + @TestForIssue( jiraKey = "HHH-15105") + public void testAccessDefaultQueryRegionStatistics() { + final Statistics statistics = sessionFactory().getStatistics(); + final CacheRegionStatistics queryRegionStatistics = statistics.getQueryRegionStatistics( + "default-query-results-region" + ); + doInHibernate( + this::sessionFactory, session -> { + List resultList = session.createQuery( "from Dog", Dog.class ) + .setCacheable( true ) + .getResultList(); + + assertEquals( 1, queryRegionStatistics.getMissCount() ); + } + ); + } + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.USE_SECOND_LEVEL_CACHE, true ); + ssrb.applySetting( AvailableSettings.USE_QUERY_CACHE, true ); + ssrb.applySetting( AvailableSettings.CACHE_REGION_FACTORY, new CachingRegionFactory() ); + ssrb.applySetting( AvailableSettings.GENERATE_STATISTICS, "true" ); + } + + @Override + protected void applyMetadataSources(MetadataSources metadataSources) { + super.applyMetadataSources( metadataSources ); + metadataSources.addAnnotatedClass( Dog.class ); + } + + @Before + public void setupData() { + doInHibernate( + this::sessionFactory, session -> { + Dog yogi = new Dog( "Yogi" ); + yogi.nickNames.add( "The Yog" ); + yogi.nickNames.add( "Little Boy" ); + yogi.nickNames.add( "Yogaroni Macaroni" ); + Dog irma = new Dog( "Irma" ); + irma.nickNames.add( "Squirmy" ); + irma.nickNames.add( "Bird" ); + session.persist( yogi ); + session.persist( irma ); + } + ); + } + + @After + public void cleanupData() { + doInHibernate( + this::sessionFactory, session -> { + List dogs = session.createQuery( "from Dog", Dog.class ).getResultList(); + for ( Dog dog : dogs ) { + session.delete( dog ); + } + } + ); + } + + @Entity(name = "Dog") + public static class Dog { + @Id + private String name; + + @ElementCollection + private Set nickNames = new HashSet<>(); + + public Dog(String name) { + this.name = name; + } + + public Dog() { + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/cache/TransactionalConcurrencyCollectionCacheEvictionTest.java b/hibernate-core/src/test/java/org/hibernate/test/cache/TransactionalConcurrencyCollectionCacheEvictionTest.java new file mode 100644 index 000000000000..6da31b69c111 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/cache/TransactionalConcurrencyCollectionCacheEvictionTest.java @@ -0,0 +1,270 @@ +/* + * 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.cache; + +import java.util.HashSet; +import java.util.Set; +import javax.persistence.Access; +import javax.persistence.AccessType; +import javax.persistence.Cacheable; +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.OneToMany; +import javax.persistence.Table; + +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; +import org.hibernate.cache.internal.CollectionCacheInvalidator; +import org.hibernate.cfg.Configuration; +import org.hibernate.cfg.Environment; + +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.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; + +/** + * @author Christian Beikov + */ +@TestForIssue(jiraKey = "HHH-4910") +public class TransactionalConcurrencyCollectionCacheEvictionTest extends BaseCoreFunctionalTestCase { + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Person.class, Phone.class }; + } + + @Before + public void before() { + CollectionCacheInvalidator.PROPAGATE_EXCEPTION = true; + } + + @After + public void after() { + CollectionCacheInvalidator.PROPAGATE_EXCEPTION = false; + } + + @Override + protected void configure(Configuration cfg) { + super.configure( cfg ); + cfg.setProperty( Environment.AUTO_EVICT_COLLECTION_CACHE, "true" ); + cfg.setProperty( Environment.USE_SECOND_LEVEL_CACHE, "true" ); + cfg.setProperty( Environment.USE_QUERY_CACHE, "false" ); + cfg.setProperty( Environment.CACHE_PROVIDER_CONFIG, "true" ); + } + + @Override + protected void prepareTest() throws Exception { + doInHibernate( + this::sessionFactory, + s -> { + Person bart = new Person( 1L, "Bart" ); + Person lisa = new Person( 2L, "Lisa" ); + Person maggie = new Person( 3L, "Maggie" ); + s.persist( bart ); + s.persist( lisa ); + s.persist( maggie ); + + bart.addPhone( "0-1122334455" ); + bart.addPhone( "0-2233445566" ); + bart.addPhone( "0-3344556677" ); + bart.addPhone( "0-4455667788" ); + bart.addPhone( "0-5566778899" ); + + lisa.addPhone( "1-1122334455" ); + lisa.addPhone( "1-2233445566" ); + lisa.addPhone( "1-3344556677" ); + lisa.addPhone( "1-4455667788" ); + lisa.addPhone( "1-5566778899" ); + + maggie.addPhone( "2-1122334455" ); + maggie.addPhone( "2-2233445566" ); + maggie.addPhone( "2-3344556677" ); + maggie.addPhone( "2-4455667788" ); + maggie.addPhone( "2-5566778899" ); + + bart.getPhones().forEach( s::persist ); + lisa.getPhones().forEach( s::persist ); + maggie.getPhones().forEach( s::persist ); + } + ); + } + + @Override + protected void cleanupTest() throws Exception { + doInHibernate( + this::sessionFactory, + s -> { + s.createQuery( "delete from Phone" ).executeUpdate(); + s.createQuery( "delete from Person" ).executeUpdate(); + } + ); + } + + @Test + public void testCollectionCacheEvictionInsert() { + doInHibernate( + this::sessionFactory, + s -> { + Person bart = s.find( Person.class, 1L ); + assertEquals( 5, bart.getPhones().size() ); + s.persist( new Phone( "test", bart ) ); + } + ); + doInHibernate( + this::sessionFactory, + s -> { + Person bart = s.find( Person.class, 1L ); + assertEquals( 6, bart.getPhones().size() ); + } + ); + } + + @Test + public void testCollectionCacheEvictionRemove() { + Long phoneId = doInHibernate( + this::sessionFactory, + s -> { + Person bart = s.find( Person.class, 1L ); + // Lazy load phones + assertEquals( 5, bart.getPhones().size() ); + return bart.getPhones().iterator().next().getId(); + } + ); + doInHibernate( + this::sessionFactory, + s -> { + s.remove( s.getReference( Phone.class, phoneId ) ); + } + ); + doInHibernate( + this::sessionFactory, + s -> { + Person bart = s.find( Person.class, 1L ); + assertEquals( 4, bart.getPhones().size() ); + } + ); + } + + @Entity(name = "Person") + @Table(name = "PERSON") + @Cacheable + @Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL) + public static class Person { + + @Id + @Access(value = AccessType.PROPERTY) + @Column(name = "PERSONID", nullable = false) + private Long id; + + @Column(name = "NAME") + private String name; + + @OneToMany(fetch = FetchType.LAZY, mappedBy = "person") + @Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL) + private final Set phones = new HashSet<>(); + + public Person() { + } + + public Person(Long id, String name) { + this.id = id; + this.name = name; + } + + 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; + } + + public Set getPhones() { + return phones; + } + + public Phone addPhone(String number) { + Phone phone = new Phone( number, this ); + getPhones().add( phone ); + return phone; + } + } + + @Entity(name = "Phone") + @Table(name = "PHONE") + @Cacheable + @Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL) + public static class Phone { + + @Id + @Access(value = AccessType.PROPERTY) + @GeneratedValue(strategy = GenerationType.AUTO) + @Column(name = "PHONEID", nullable = false) + private Long id; + + @Column(name = "PHONE_NUMBER") + private String number; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "PERSONID") + private Person person; + + public Phone() { + } + + public Phone(String number, Person person) { + this.number = number; + this.person = person; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getNumber() { + return number; + } + + public void setNumber(String number) { + this.number = number; + } + + public Person getPerson() { + return person; + } + + public void setPerson(Person person) { + this.person = person; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/cascade/circle/delete/Address.java b/hibernate-core/src/test/java/org/hibernate/test/cascade/circle/delete/Address.java new file mode 100644 index 000000000000..122072af6138 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/cascade/circle/delete/Address.java @@ -0,0 +1,49 @@ +package org.hibernate.test.cascade.circle.delete; + +import javax.persistence .Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +@Entity +public class Address { + @Id + @GeneratedValue + private Long id; + + @ManyToOne + private Person person; + + private String description; + + public Address() { + } + + public Address(String description) { + this.description = description; + } + + public Long getId() { + return id; + } + + public void setId(final Long id) { + this.id = id; + } + + public Person getPerson() { + return person; + } + + public void setPerson(Person person) { + this.person = person; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/cascade/circle/delete/CascadeDeleteTest.java b/hibernate-core/src/test/java/org/hibernate/test/cascade/circle/delete/CascadeDeleteTest.java new file mode 100644 index 000000000000..8651bd4a8736 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/cascade/circle/delete/CascadeDeleteTest.java @@ -0,0 +1,46 @@ +package org.hibernate.test.cascade.circle.delete; + +import java.util.List; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Before; +import org.junit.Test; + +@TestForIssue(jiraKey = "HHH-15218") +public class CascadeDeleteTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[]{ + Address.class, + Person.class + }; + } + + @Before + public void setUp() { + inTransaction( + session -> { + Person person = new Person(); + + Address currentAddress = new Address( "Localita S. Egidio Gradoli (VT)" ); + person.addCurrentAddress( currentAddress ); + + session.persist( person ); + } + ); + } + + @Test + public void testDelete() { + inTransaction( + session -> { + List people = session.createQuery( "from Person", Person.class ).list(); + people.forEach( person -> { + session.remove( person ); + } ); + } + ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/cascade/circle/delete/Person.java b/hibernate-core/src/test/java/org/hibernate/test/cascade/circle/delete/Person.java new file mode 100644 index 000000000000..029490ba6a50 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/cascade/circle/delete/Person.java @@ -0,0 +1,66 @@ +package org.hibernate.test.cascade.circle.delete; + +import java.util.ArrayList; +import java.util.List; + +import org.hibernate.annotations.DynamicUpdate; +import org.hibernate.annotations.OptimisticLockType; +import org.hibernate.annotations.OptimisticLocking; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.OneToMany; +import javax.persistence.OneToOne; + +@Entity +@OptimisticLocking(type = OptimisticLockType.DIRTY) +@DynamicUpdate +public class Person { + @Id + @GeneratedValue + private Long id; + + private String name; + + @OneToMany(cascade = CascadeType.ALL, mappedBy = "person") + private final List

      addresses = new ArrayList<>(); + + @OneToOne(cascade = { CascadeType.ALL }) + private Address currentAddress; + + public Long getId() { + return id; + } + + public void setId(final Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List
      getAddresses() { + return addresses; + } + + public Address getCurrentAddress() { + return currentAddress; + } + + public void setCurrentAddress(Address currentAddress) { + this.currentAddress = currentAddress; + } + + public void addCurrentAddress(Address currentAddress){ + this.addresses.add( currentAddress ); + currentAddress.setPerson( this ); + this.currentAddress = currentAddress; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/collection/list/PersistentListTest.java b/hibernate-core/src/test/java/org/hibernate/test/collection/list/PersistentListTest.java index 6f315ba71d43..6712614db28c 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/collection/list/PersistentListTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/collection/list/PersistentListTest.java @@ -131,7 +131,7 @@ public void execute(Connection connection) throws SQLException { final QueryableCollection queryableCollection = (QueryableCollection) collectionPersister; SimpleSelect select = new SimpleSelect( getDialect() ) .setTableName( queryableCollection.getTableName() ) - .addColumn( "ORDER_ID" ) + .addColumn( "order_id" ) .addColumn( "INDX" ) .addColumn( "PRD_CODE" ); PreparedStatement preparedStatement = ((SessionImplementor)session2).getJdbcCoordinator().getStatementPreparer().prepareStatement( select.toStatementString() ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/collection/map/Mappings.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/collection/map/Mappings.hbm.xml index a0815864dbb9..0c44de49bbce 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/collection/map/Mappings.hbm.xml +++ b/hibernate-core/src/test/java/org/hibernate/test/collection/map/Mappings.hbm.xml @@ -24,7 +24,7 @@ - + diff --git a/hibernate-core/src/test/java/org/hibernate/test/collection/set/Mappings.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/collection/set/Mappings.hbm.xml index 6c446b8d3e50..a364109d2afd 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/collection/set/Mappings.hbm.xml +++ b/hibernate-core/src/test/java/org/hibernate/test/collection/set/Mappings.hbm.xml @@ -22,7 +22,7 @@ - + diff --git a/hibernate-core/src/test/java/org/hibernate/test/collection/set/MappingsNonLazy.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/collection/set/MappingsNonLazy.hbm.xml index df17f526c903..36ba14773290 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/collection/set/MappingsNonLazy.hbm.xml +++ b/hibernate-core/src/test/java/org/hibernate/test/collection/set/MappingsNonLazy.hbm.xml @@ -22,7 +22,7 @@ - + diff --git a/hibernate-core/src/test/java/org/hibernate/test/component/basic/ComponentTest.java b/hibernate-core/src/test/java/org/hibernate/test/component/basic/ComponentTest.java index a176368a688a..ea80dd6615af 100755 --- a/hibernate-core/src/test/java/org/hibernate/test/component/basic/ComponentTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/component/basic/ComponentTest.java @@ -232,7 +232,7 @@ public void testCustomColumnReadAndWrite() { // Value returned by Oracle native query is a Types.NUMERIC, which is mapped to a BigDecimalType; // Cast returned value to Number then call Number.doubleValue() so it works on all dialects. Double heightViaSql = - ( (Number)s.createSQLQuery("select height_centimeters from T_USER where T_USER.username='steve'").uniqueResult()) + ( (Number)s.createSQLQuery("select height_centimeters from T_USER where T_USER.userName='steve'").uniqueResult()) .doubleValue(); assertEquals(HEIGHT_CENTIMETERS, heightViaSql, 0.01d); @@ -257,7 +257,7 @@ public void testCustomColumnReadAndWrite() { u.getPerson().setHeightInches(1); s.flush(); heightViaSql = - ( (Number)s.createSQLQuery("select height_centimeters from T_USER where T_USER.username='steve'").uniqueResult() ) + ( (Number)s.createSQLQuery("select height_centimeters from T_USER where T_USER.userName='steve'").uniqueResult() ) .doubleValue(); assertEquals(2.54d, heightViaSql, 0.01d); s.delete(u); diff --git a/hibernate-core/src/test/java/org/hibernate/test/converter/lob/ConverterAndLobTest.java b/hibernate-core/src/test/java/org/hibernate/test/converter/lob/ConverterAndLobTest.java index 3382aaa8ef7b..b3c97e7471ed 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/converter/lob/ConverterAndLobTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/converter/lob/ConverterAndLobTest.java @@ -10,7 +10,9 @@ import org.hibernate.Session; import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.SybaseASE15Dialect; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; import org.junit.Test; @@ -25,6 +27,7 @@ public class ConverterAndLobTest extends BaseNonConfigCoreFunctionalTestCase { @Test @TestForIssue( jiraKey = "HHH-9615" ) + @SkipForDialect( value = SybaseASE15Dialect.class, comment = "jTDS driver doesn't implement binary stream handling") @SuppressWarnings("unchecked") public void basicTest() { diff --git a/hibernate-core/src/test/java/org/hibernate/test/criterion/NationalizedIgnoreCaseTest.java b/hibernate-core/src/test/java/org/hibernate/test/criterion/NationalizedIgnoreCaseTest.java index 7f10541705be..6451c043ea73 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/criterion/NationalizedIgnoreCaseTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/criterion/NationalizedIgnoreCaseTest.java @@ -17,6 +17,7 @@ import org.hibernate.dialect.DB2Dialect; import org.hibernate.dialect.DerbyDialect; import org.hibernate.dialect.PostgreSQL81Dialect; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; @@ -33,6 +34,7 @@ @SkipForDialect(value = DB2Dialect.class, comment = "DB2 jdbc driver doesn't support setNString") @SkipForDialect(value = DerbyDialect.class, comment = "Derby jdbc driver doesn't support setNString") @SkipForDialect(value = PostgreSQL81Dialect.class, comment = "PostgreSQL jdbc driver doesn't support setNString") +@SkipForDialect(value = SybaseASE15Dialect.class, comment = "jTDS jdbc driver doesn't support setNString") public class NationalizedIgnoreCaseTest extends BaseCoreFunctionalTestCase { @Override diff --git a/hibernate-core/src/test/java/org/hibernate/test/cut/ImmutableCompositeUserTypeTest.java b/hibernate-core/src/test/java/org/hibernate/test/cut/ImmutableCompositeUserTypeTest.java new file mode 100644 index 000000000000..58bf6399026c --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/cut/ImmutableCompositeUserTypeTest.java @@ -0,0 +1,86 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.cut; + +import java.math.BigDecimal; +import java.util.Currency; +import java.util.List; + + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.hibernate.annotations.Columns; +import org.hibernate.annotations.Type; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +@TestForIssue( jiraKey = "HHH-15554") +public class ImmutableCompositeUserTypeTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[]{ Wallet.class}; + } + + @Test + public void testImmutableCutMerge() { + inTransaction( + session -> { + ImmutableMonetoryAmount monetoryAmount = new ImmutableMonetoryAmount( + new BigDecimal( 1.5 ), + Currency.getInstance( "USD" ) + ); + Wallet wallet = new Wallet( 1, monetoryAmount ); + session.merge( wallet ); + + List wallets = session.createQuery( + "from Wallet w where w.amount.amount > 1.0 and w.amount.currency = 'USD'" ).list(); + assertThat( wallets.size() ).isEqualTo( 1 ); + } + ); + } + + @Entity(name = "Wallet") + @Table(name = "Wallet_TABLE") + public static class Wallet { + @Id + private Integer id; + + @Type(type = "org.hibernate.test.cut.ImmutableMonetoryAmountUserType") + @Columns(columns = { + @Column(name = "amount"), + @Column(name = "currency"), + }) + private ImmutableMonetoryAmount amount; + + public Wallet() { + } + + public Wallet(Integer id, ImmutableMonetoryAmount amount) { + this.id = id; + this.amount = amount; + } + + public Integer getId() { + return id; + } + + public ImmutableMonetoryAmount getAmount() { + return amount; + } + + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/cut/ImmutableMonetoryAmount.java b/hibernate-core/src/test/java/org/hibernate/test/cut/ImmutableMonetoryAmount.java new file mode 100644 index 000000000000..7d41981f38e9 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/cut/ImmutableMonetoryAmount.java @@ -0,0 +1,26 @@ +package org.hibernate.test.cut; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Currency; + +public class ImmutableMonetoryAmount implements Serializable { + + private BigDecimal amount; + private Currency currency; + + public ImmutableMonetoryAmount(BigDecimal amount, Currency currency) { + this.amount = amount; + this.currency = currency; + } + + public BigDecimal getAmount() { + return amount; + } + + + public Currency getCurrency() { + return currency; + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/cut/ImmutableMonetoryAmountUserType.java b/hibernate-core/src/test/java/org/hibernate/test/cut/ImmutableMonetoryAmountUserType.java new file mode 100644 index 000000000000..caf5faf97c81 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/cut/ImmutableMonetoryAmountUserType.java @@ -0,0 +1,114 @@ +package org.hibernate.test.cut; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Currency; + +import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.type.StandardBasicTypes; +import org.hibernate.type.Type; +import org.hibernate.usertype.CompositeUserType; + +public class ImmutableMonetoryAmountUserType implements CompositeUserType { + + @Override + public String[] getPropertyNames() { + return new String[] { "amount", "currency" }; + } + + @Override + public Type[] getPropertyTypes() { + return new Type[] { StandardBasicTypes.BIG_DECIMAL, StandardBasicTypes.CURRENCY }; + } + + @Override + public Object getPropertyValue(Object component, int property) throws HibernateException { + ImmutableMonetoryAmount ma = (ImmutableMonetoryAmount) component; + return property==0 ? ma.getAmount() : ma.getCurrency(); + } + + @Override + public void setPropertyValue(Object component, int property, Object value) + throws HibernateException { + throw new UnsupportedOperationException("immutable"); + } + + @Override + public Class returnedClass() { + return ImmutableMonetoryAmount.class; + } + + @Override + public boolean equals(Object x, Object y) throws HibernateException { + if (x==y) return true; + if (x==null || y==null) return false; + ImmutableMonetoryAmount mx = (ImmutableMonetoryAmount) x; + ImmutableMonetoryAmount my = (ImmutableMonetoryAmount) y; + return mx.getAmount().equals( my.getAmount() ) && + mx.getCurrency().equals( my.getCurrency() ); + } + + @Override + public int hashCode(Object x) throws HibernateException { + return ( (ImmutableMonetoryAmount) x ).getAmount().hashCode(); + } + + @Override + public Object nullSafeGet( + ResultSet rs, + String[] names, + SharedSessionContractImplementor session, + Object owner) throws HibernateException, SQLException { + BigDecimal amt = StandardBasicTypes.BIG_DECIMAL.nullSafeGet( rs, names[0], session ); + Currency cur = StandardBasicTypes.CURRENCY.nullSafeGet( rs, names[1], session ); + if (amt==null) return null; + return new ImmutableMonetoryAmount(amt, cur); + } + + @Override + public void nullSafeSet( + PreparedStatement st, + Object value, + int index, + SharedSessionContractImplementor session) throws HibernateException, SQLException { + ImmutableMonetoryAmount ma = (ImmutableMonetoryAmount) value; + BigDecimal amt = ma == null ? null : ma.getAmount(); + Currency cur = ma == null ? null : ma.getCurrency(); + StandardBasicTypes.BIG_DECIMAL.nullSafeSet(st, amt, index, session); + StandardBasicTypes.CURRENCY.nullSafeSet(st, cur, index+1, session); + } + + @Override + public Object deepCopy(Object value) throws HibernateException { + ImmutableMonetoryAmount ma = (ImmutableMonetoryAmount) value; + return new ImmutableMonetoryAmount( ma.getAmount(), ma.getCurrency() ); + } + + @Override + public boolean isMutable() { + return false; + } + + @Override + public Serializable disassemble(Object value, SharedSessionContractImplementor session) + throws HibernateException { + return (Serializable) deepCopy(value); + } + + @Override + public Object assemble(Serializable cached, SharedSessionContractImplementor session, Object owner) + throws HibernateException { + return deepCopy(cached); + } + + @Override + public Object replace(Object original, Object target, SharedSessionContractImplementor session, Object owner) + throws HibernateException { + return deepCopy(original); //TODO: improve + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/dialect/functional/MariaDBExtractSequenceInformationTest.java b/hibernate-core/src/test/java/org/hibernate/test/dialect/functional/MariaDBExtractSequenceInformationTest.java new file mode 100644 index 000000000000..0f91ec8b2178 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/dialect/functional/MariaDBExtractSequenceInformationTest.java @@ -0,0 +1,98 @@ +package org.hibernate.test.dialect.functional; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.stream.StreamSupport; + +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Environment; +import org.hibernate.dialect.MariaDBDialect; +import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess; +import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; +import org.hibernate.engine.jdbc.spi.JdbcServices; +import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorMariaDBDatabaseImpl; +import org.hibernate.tool.schema.extract.spi.ExtractionContext; +import org.hibernate.tool.schema.extract.spi.SequenceInformation; +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; + +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInAutoCommit; + +/** + * @author Jan Schatteman + */ +@RequiresDialect(value = MariaDBDialect.class) +public class MariaDBExtractSequenceInformationTest extends BaseCoreFunctionalTestCase { + + private final static String hhh15665SeqName = "HHH-15665-seq"; + + private final static Map settings = new HashMap<>(3); + + static { + settings.put( AvailableSettings.URL, Environment.getProperties().getProperty( AvailableSettings.URL ) ); + settings.put( AvailableSettings.USER, Environment.getProperties().getProperty( AvailableSettings.USER ) ); + settings.put( AvailableSettings.PASS, Environment.getProperties().getProperty( AvailableSettings.PASS ) ); + } + + @BeforeClass + public static void setUp() throws Exception { + doInAutoCommit( settings, "CREATE SEQUENCE IF NOT EXISTS `" + hhh15665SeqName + "`" ); + } + + @AfterClass + public static void tearDown() throws SQLException { + doInAutoCommit( settings, "DROP SEQUENCE IF EXISTS `" + hhh15665SeqName + "`" ); + } + + @Test + @TestForIssue( jiraKey = "HHH-15665" ) + public void testExtractSequenceInformationForSqlServerWithCaseSensitiveCollation() { + StandardServiceRegistry ssr = new StandardServiceRegistryBuilder().applySettings( settings ).build(); + JdbcEnvironment jdbcEnvironment = ssr.getService( JdbcEnvironment.class ); + JdbcConnectionAccess bootstrapJdbcConnectionAccess = ssr.getService( JdbcServices.class ).getBootstrapJdbcConnectionAccess(); + + try ( Connection connection = bootstrapJdbcConnectionAccess.obtainConnection() ) { + Iterable sequenceInformations = SequenceInformationExtractorMariaDBDatabaseImpl.INSTANCE.extractMetadata( + new ExtractionContext.EmptyExtractionContext() { + @Override + public Connection getJdbcConnection() { + return connection; + } + + @Override + public JdbcEnvironment getJdbcEnvironment() { + return jdbcEnvironment; + } + } ); + + Assert.assertNotNull( sequenceInformations ); + + Optional seq = StreamSupport.stream( sequenceInformations.spliterator(), false ) + .filter( + sequence -> hhh15665SeqName.equals( sequence.getSequenceName() + .getSequenceName() + .getText() ) + ) + .findFirst(); + + Assert.assertTrue( hhh15665SeqName + " not found", seq.isPresent() ); + } + catch ( SQLException e ) { + Assert.fail( "Sequence information for " + hhh15665SeqName + " was not retrieved: " + e.getMessage() ); + } + finally { + StandardServiceRegistryBuilder.destroy( ssr ); + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/dialect/functional/OracleDialectSequenceInformationTest.java b/hibernate-core/src/test/java/org/hibernate/test/dialect/functional/OracleDialectSequenceInformationTest.java new file mode 100644 index 000000000000..11bdfe869798 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/dialect/functional/OracleDialectSequenceInformationTest.java @@ -0,0 +1,103 @@ +/* + * 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.dialect.functional; + +import java.math.BigDecimal; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Optional; +import java.util.stream.StreamSupport; + +import org.hibernate.dialect.Oracle8iDialect; +import org.hibernate.dialect.OracleDialect; +import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorOracleDatabaseImpl; +import org.hibernate.tool.schema.extract.spi.ExtractionContext; +import org.hibernate.tool.schema.extract.spi.SequenceInformation; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInAutoCommit; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +@RequiresDialect(value = { Oracle8iDialect.class }) +@TestForIssue(jiraKey = "HHH-13694") +public class OracleDialectSequenceInformationTest extends BaseNonConfigCoreFunctionalTestCase { + + private static final String MIN_SEQUENCE_NAME = "SEQ_MIN_TEST"; + private static final String MAX_SEQUENCE_NAME = "SEQ_MAX_TEST"; + private static final String MIN_VALUE = "-99999999999999999999999999"; + private static final String MAX_VALUE = "99999999999999999999999999"; + + @Before + public void prepareTest() throws Exception { + doInAutoCommit( + "DROP SEQUENCE " + MIN_SEQUENCE_NAME, + "CREATE SEQUENCE " + MIN_SEQUENCE_NAME + " MINVALUE " + MIN_VALUE + " MAXVALUE -1 INCREMENT BY -1", + "DROP SEQUENCE " + MAX_SEQUENCE_NAME, + "CREATE SEQUENCE " + MAX_SEQUENCE_NAME + " MINVALUE 0 MAXVALUE " + MAX_VALUE + " INCREMENT BY 1" ); + } + + @After + public void cleanupTest() throws Exception { + doInAutoCommit( + "DROP SEQUENCE " + MIN_SEQUENCE_NAME, + "DROP SEQUENCE " + MAX_SEQUENCE_NAME ); + } + + @Test + public void testExtractSequenceWithMinValueLowerThanLongMinValue() throws SQLException { + SequenceInformation sequence = fetchSequenceInformation( MIN_SEQUENCE_NAME ); + + assertEquals( -1L, sequence.getIncrementValue().longValue() ); + assertEquals( Long.MIN_VALUE, sequence.getMinValue().longValue() ); + } + + @Test + public void testExtractSequenceWithMaxValueGreaterThanLongMaxValue() throws SQLException { + SequenceInformation sequence = fetchSequenceInformation( MAX_SEQUENCE_NAME ); + + assertEquals( 1L, sequence.getIncrementValue().longValue() ); + assertEquals( Long.MAX_VALUE, sequence.getMaxValue().longValue() ); + } + + private SequenceInformation fetchSequenceInformation(String sequenceName) throws SQLException { + try ( Connection connection = sessionFactory().getJdbcServices() + .getBootstrapJdbcConnectionAccess() + .obtainConnection() ) { + JdbcEnvironment jdbcEnvironment = sessionFactory().getJdbcServices().getJdbcEnvironment(); + SequenceInformationExtractorOracleDatabaseImpl sequenceExtractor = SequenceInformationExtractorOracleDatabaseImpl.INSTANCE; + Iterable sequenceInformations = sequenceExtractor.extractMetadata( + new ExtractionContext.EmptyExtractionContext() { + + @Override + public Connection getJdbcConnection() { + return connection; + } + + @Override + public JdbcEnvironment getJdbcEnvironment() { + return jdbcEnvironment; + } + } ); + + // lets skip system sequences + Optional foundSequence = StreamSupport.stream( sequenceInformations.spliterator(), false ) + .filter( sequence -> sequenceName.equals( sequence.getSequenceName().getSequenceName().getText().toUpperCase() ) ) + .findFirst(); + + assertTrue( sequenceName + " not found", foundSequence.isPresent() ); + + return foundSequence.get(); + } + } +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/exception/SQLExceptionConversionTest.java b/hibernate-core/src/test/java/org/hibernate/test/exception/SQLExceptionConversionTest.java index 9aca51c95579..ee5ba59bb0e8 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/exception/SQLExceptionConversionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/exception/SQLExceptionConversionTest.java @@ -16,6 +16,7 @@ import org.hibernate.Session; import org.hibernate.dialect.AbstractHANADialect; import org.hibernate.dialect.MySQLMyISAMDialect; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.engine.jdbc.spi.JdbcCoordinator; import org.hibernate.engine.jdbc.spi.ResultSetReturn; import org.hibernate.engine.jdbc.spi.StatementPreparer; @@ -44,6 +45,7 @@ public String[] getMappings() { value = { MySQLMyISAMDialect.class, AbstractHANADialect.class }, comment = "MySQL (MyISAM) / Hana do not support FK violation checking" ) + @SkipForDialect(value = SybaseASE15Dialect.class, comment = "Missing constraint violation extractor") public void testIntegrityViolation() throws Exception { final Session session = openSession(); session.beginTransaction(); @@ -110,6 +112,7 @@ public void execute(Connection connection) throws SQLException { @Test @TestForIssue(jiraKey = "HHH-7357") + @SkipForDialect(value = SybaseASE15Dialect.class, comment = "Missing constraint violation extractor") public void testNotNullConstraint() { final Session session = openSession(); session.beginTransaction(); diff --git a/hibernate-core/src/test/java/org/hibernate/test/foreignkeys/disabled/InheritanceManyToManyForeignKeyTest.java b/hibernate-core/src/test/java/org/hibernate/test/foreignkeys/disabled/InheritanceManyToManyForeignKeyTest.java index 7da4197f584f..0a76fc0b93dd 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/foreignkeys/disabled/InheritanceManyToManyForeignKeyTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/foreignkeys/disabled/InheritanceManyToManyForeignKeyTest.java @@ -21,7 +21,9 @@ import org.hibernate.Session; import org.hibernate.Transaction; +import org.hibernate.dialect.SybaseDialect; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; import org.junit.Test; @@ -44,6 +46,7 @@ protected Class[] getAnnotatedClasses() { } @Test + @SkipForDialect(value = SybaseDialect.class, comment = "Only dates between January 1, 1753 and December 31, 9999 are accepted.") public void testForeignKeyNameUnicity() { Session session = openSession(); Transaction transaction = session.beginTransaction(); diff --git a/hibernate-core/src/test/java/org/hibernate/test/generated/MSSQLGeneratedPropertyEntity.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/generated/MSSQLGeneratedPropertyEntity.hbm.xml index c1243813818f..3fa1797c5cc8 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/generated/MSSQLGeneratedPropertyEntity.hbm.xml +++ b/hibernate-core/src/test/java/org/hibernate/test/generated/MSSQLGeneratedPropertyEntity.hbm.xml @@ -18,7 +18,7 @@ - + diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/IndicesTest.java b/hibernate-core/src/test/java/org/hibernate/test/hql/IndicesTest.java index ddeae05664ee..7720cd356889 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/hql/IndicesTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/hql/IndicesTest.java @@ -96,6 +96,7 @@ public Project(Integer id) { } @Entity(name = "Role") + @Table(name = "proj_role") public static class Role { @Id diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/QuerySplitterTest.java b/hibernate-core/src/test/java/org/hibernate/test/hql/QuerySplitterTest.java index 54f3015169e1..a288c65312ff 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/hql/QuerySplitterTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/hql/QuerySplitterTest.java @@ -61,17 +61,22 @@ public void testMemoryConsumptionOfFailedImportsCache() throws NoSuchFieldExcept sessionFactory() ) ); - MetamodelImpl metamodel = (MetamodelImpl) sessionFactory().getMetamodel(); + Map validImports = extractMapFromMetamodel("knownValidImports"); + Map invalidImports = extractMapFromMetamodel("knownInvalidImports"); - Field field = MetamodelImpl.class.getDeclaredField( "imports" ); - field.setAccessible( true ); - - //noinspection unchecked - Map imports = (Map) field.get( metamodel ); + assertEquals( 2, validImports.size() ); // VERY hard-coded, but considering the possibility of a regression of a memory-related issue, // it should be worth it - assertEquals( 1000, imports.size() ); + assertEquals( 1_000, invalidImports.size() ); + } + + private Map extractMapFromMetamodel(String fieldName) throws NoSuchFieldException, IllegalAccessException { + MetamodelImpl metamodel = (MetamodelImpl) sessionFactory().getMetamodel(); + Field field = MetamodelImpl.class.getDeclaredField( fieldName ); + field.setAccessible( true ); + //noinspection unchecked + return (Map) field.get( metamodel ); } @Test diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/VirtualKeyManyToOnePropertyPathTest.java b/hibernate-core/src/test/java/org/hibernate/test/hql/VirtualKeyManyToOnePropertyPathTest.java new file mode 100644 index 000000000000..d61fb5182115 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/hql/VirtualKeyManyToOnePropertyPathTest.java @@ -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 . + */ +package org.hibernate.test.hql; + +import java.io.Serializable; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +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.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertFalse; + +/** + * @author Christian Beikov + */ +public class VirtualKeyManyToOnePropertyPathTest extends BaseNonConfigCoreFunctionalTestCase { + + private SQLStatementInterceptor sqlStatementInterceptor; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] {Item.class, OrderItem.class}; + } + + @Override + protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) { + sqlStatementInterceptor = new SQLStatementInterceptor( sfb ); + } + + @Test + @TestForIssue(jiraKey = "HHH-15051") + public void tstPropertyPathVirtualIdOfKeyManyToOneProducesNoJoin() { + doInHibernate( this::sessionFactory, session -> { + sqlStatementInterceptor.clear(); + session.createQuery( "SELECT o.item.id1 FROM OrderItem o", Long.class ).getResultList(); + sqlStatementInterceptor.assertExecutedCount( 1 ); + assertFalse( sqlStatementInterceptor.getSqlQueries().get( 0 ).contains( " join " ) ); + } ); + } + + @Entity(name = "Item") + public static class Item implements Serializable { + @Id + Long id1; + @Id + Long id2; + + public Item() { + } + + } + + @Entity(name = "OrderItem") + public static class OrderItem implements Serializable { + @Id + long id; + @Id + @ManyToOne + Item item; + + public OrderItem() { + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/size/filter/WhereAnnotatedOneToManySizeTest.java b/hibernate-core/src/test/java/org/hibernate/test/hql/size/filter/WhereAnnotatedOneToManySizeTest.java index a3ec5d5a3587..b9a2377a1965 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/hql/size/filter/WhereAnnotatedOneToManySizeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/hql/size/filter/WhereAnnotatedOneToManySizeTest.java @@ -12,6 +12,7 @@ import org.hibernate.dialect.AbstractHANADialect; import org.hibernate.dialect.DB2Dialect; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.query.spi.QueryImplementor; import org.hibernate.testing.SkipForDialect; @@ -91,6 +92,7 @@ public void after() { @Test @SkipForDialect(value = DB2Dialect.class, comment = "DB2 does not support correlated subqueries in the ORDER BY clause") @SkipForDialect(value = AbstractHANADialect.class, comment = "HANA db does not support correlated subqueries in the ORDER BY clause") + @SkipForDialect(value = SybaseASE15Dialect.class, comment = "Sybase does not support correlated subqueries in the ORDER BY clause") public void orderBy_sizeOf() { inSession( session -> { QueryImplementor query = session.createQuery( @@ -104,6 +106,7 @@ public void orderBy_sizeOf() { @Test @SkipForDialect(value = DB2Dialect.class, comment = "DB2 does not support correlated subqueries in the ORDER BY clause") @SkipForDialect(value = AbstractHANADialect.class, comment = "HANA db does not support correlated subqueries in the ORDER BY clause") + @SkipForDialect(value = SybaseASE15Dialect.class, comment = "Sybase does not support correlated subqueries in the ORDER BY clause") public void orderBy_dotSize() { inSession( session -> { QueryImplementor query = session.createQuery( diff --git a/hibernate-core/src/test/java/org/hibernate/test/id/sequence/PostgreSQLIdentitySequenceTest.java b/hibernate-core/src/test/java/org/hibernate/test/id/sequence/PostgreSQLIdentitySequenceTest.java deleted file mode 100644 index 9f47800c869a..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/id/sequence/PostgreSQLIdentitySequenceTest.java +++ /dev/null @@ -1,112 +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 . - */ -package org.hibernate.test.id.sequence; - -import java.sql.Connection; -import java.sql.SQLException; -import java.sql.Statement; -import java.util.EnumSet; -import java.util.Map; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.SequenceGenerator; - -import org.hibernate.boot.MetadataSources; -import org.hibernate.boot.registry.StandardServiceRegistryBuilder; -import org.hibernate.boot.spi.MetadataImplementor; -import org.hibernate.cfg.AvailableSettings; -import org.hibernate.cfg.Environment; -import org.hibernate.dialect.PostgreSQL10Dialect; -import org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl; -import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; -import org.hibernate.service.ServiceRegistry; -import org.hibernate.tool.hbm2ddl.SchemaExport; -import org.hibernate.tool.schema.TargetType; - -import org.hibernate.testing.RequiresDialect; -import org.junit.Test; - -import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; -import static org.junit.Assert.fail; - -/** - * @author Vlad Mhalcea - */ -@RequiresDialect(jiraKey = "HHH-13106", value = PostgreSQL10Dialect.class) -public class PostgreSQLIdentitySequenceTest extends BaseEntityManagerFunctionalTestCase { - - @Override - protected Class[] getAnnotatedClasses() { - return new Class[] { Role.class }; - } - - private DriverManagerConnectionProviderImpl connectionProvider; - - @Override - public void buildEntityManagerFactory() { - connectionProvider = new DriverManagerConnectionProviderImpl(); - connectionProvider.configure( Environment.getProperties() ); - - try(Connection connection = connectionProvider.getConnection(); - Statement statement = connection.createStatement()) { - statement.execute( "DROP TABLE IF EXISTS roles CASCADE" ); - statement.execute( "CREATE TABLE roles ( id BIGINT NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY )" ); - } - catch (SQLException e) { - fail(e.getMessage()); - } - - super.buildEntityManagerFactory(); - } - - @Override - public void releaseResources() { - super.releaseResources(); - - try(Connection connection = connectionProvider.getConnection(); - Statement statement = connection.createStatement()) { - statement.execute( "DROP TABLE IF EXISTS roles CASCADE" ); - } - catch (SQLException e) { - fail(e.getMessage()); - } - - if ( connectionProvider != null ) { - connectionProvider.stop(); - } - } - - @Test - public void test() { - doInJPA( this::entityManagerFactory, entityManager -> { - Role role = new Role(); - entityManager.persist( role ); - } ); - } - - @Entity(name = "Role") - public static class Role { - - @Id - @Column(name = "id") - @SequenceGenerator(name = "roles_id_seq", sequenceName = "roles_id_seq", allocationSize = 1) - @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "roles_id_seq") - private Long id; - - public Long getId() { - return id; - } - - public void setId(final Long id) { - this.id = id; - } - } - -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/idgen/enhanced/forcedtable/PooledForcedTableSequenceTest.java b/hibernate-core/src/test/java/org/hibernate/test/idgen/enhanced/forcedtable/PooledForcedTableSequenceTest.java index fd9d68fd47d0..2e7df00aa0dc 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/idgen/enhanced/forcedtable/PooledForcedTableSequenceTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/idgen/enhanced/forcedtable/PooledForcedTableSequenceTest.java @@ -7,6 +7,7 @@ package org.hibernate.test.idgen.enhanced.forcedtable; import org.junit.Test; +import org.junit.jupiter.api.Assertions; import org.hibernate.Session; import org.hibernate.id.IdentifierGeneratorHelper.BasicHolder; @@ -15,6 +16,7 @@ import org.hibernate.id.enhanced.TableStructure; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.hibernate.testing.transaction.TransactionUtil; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -23,6 +25,9 @@ * @author Steve Ebersole */ public class PooledForcedTableSequenceTest extends BaseCoreFunctionalTestCase { + + private static final long INITIAL_VALUE = 1; + public String[] getMappings() { return new String[] { "idgen/enhanced/forcedtable/Pooled.hbm.xml" }; } @@ -46,37 +51,47 @@ public void testNormalBoundary() { PooledOptimizer optimizer = (PooledOptimizer) generator.getOptimizer(); int increment = optimizer.getIncrementSize(); - Entity[] entities = new Entity[ increment + 2 ]; - Session s = openSession(); - s.beginTransaction(); - for ( int i = 0; i <= increment; i++ ) { - entities[i] = new Entity( "" + ( i + 1 ) ); - s.save( entities[i] ); - long expectedId = i + 1; - assertEquals( expectedId, entities[i].getId().longValue() ); - // NOTE : initialization calls table twice - assertEquals( 2, generator.getDatabaseStructure().getTimesAccessed() ); - assertEquals( increment + 1, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() ); - assertEquals( i + 1, ( (BasicHolder) optimizer.getLastValue() ).getActualLongValue() ); - assertEquals( increment + 1, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() ); - } - // now force a "clock over" - entities[increment + 1] = new Entity( "" + increment ); - s.save( entities[increment + 1] ); - long expectedId = optimizer.getIncrementSize() + 2; - assertEquals( expectedId, entities[ increment + 1 ].getId().longValue() ); - // initialization (2) + clock over - assertEquals( 3, generator.getDatabaseStructure().getTimesAccessed() ); - assertEquals( ( increment * 2 ) + 1, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() ); - assertEquals( increment + 2, ( (BasicHolder) optimizer.getLastValue() ).getActualLongValue() ); - s.getTransaction().commit(); - s.beginTransaction(); - for ( int i = 0; i < entities.length; i++ ) { - assertEquals( i + 1, entities[i].getId().intValue() ); - s.delete( entities[i] ); - } - s.getTransaction().commit(); - s.close(); + TransactionUtil.doInHibernate( + this::sessionFactory, + s -> { + // The value that we get from the callback is the high value (PooledOptimizer by default) + // When first increment is initialValue, we can only generate one id from it -> id 1 + Entity entity = new Entity( "" + INITIAL_VALUE ); + s.save( entity ); + + long expectedId = INITIAL_VALUE; + assertEquals( expectedId, entity.getId().longValue() ); + assertEquals( 1, generator.getDatabaseStructure().getTimesAccessed() ); + assertEquals( INITIAL_VALUE, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() ); + assertEquals( INITIAL_VALUE, ( (BasicHolder) optimizer.getLastValue() ).getActualLongValue() ); + assertEquals( INITIAL_VALUE, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() ); + + // now start a full range of values, callback give us hiValue 11 + // id : 2,3,4...,11 + for ( int i = 1; i <= increment; i++ ) { + entity = new Entity( "" + ( i + INITIAL_VALUE ) ); + s.save( entity ); + + expectedId = i + INITIAL_VALUE; + assertEquals( expectedId, entity.getId().longValue() ); + assertEquals( 2, generator.getDatabaseStructure().getTimesAccessed() ); + assertEquals( increment + 1, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() ); + assertEquals( expectedId, ( (BasicHolder) optimizer.getLastValue() ).getActualLongValue() ); + } + + // now force a "clock over" + expectedId++; + entity = new Entity( "" + expectedId ); + s.save( entity ); + + assertEquals( expectedId, entity.getId().longValue() ); + assertEquals( 3, generator.getDatabaseStructure().getTimesAccessed() ); + assertEquals( increment * 2L + 1, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() ); + assertEquals( expectedId, ( (BasicHolder) optimizer.getLastValue() ).getActualLongValue() ); + + s.createQuery( "delete Entity" ).executeUpdate(); + } + ); } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/idgen/enhanced/sequence/PooledSequenceTest.java b/hibernate-core/src/test/java/org/hibernate/test/idgen/enhanced/sequence/PooledSequenceTest.java index 79f644cc58dd..b84463d19186 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/idgen/enhanced/sequence/PooledSequenceTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/idgen/enhanced/sequence/PooledSequenceTest.java @@ -13,6 +13,7 @@ import org.hibernate.id.enhanced.SequenceStyleGenerator; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.hibernate.testing.transaction.TransactionUtil; import static org.hibernate.id.IdentifierGeneratorHelper.BasicHolder; import static org.hibernate.testing.junit4.ExtraAssertions.assertClassAssignability; @@ -22,6 +23,9 @@ * @author Steve Ebersole */ public class PooledSequenceTest extends BaseCoreFunctionalTestCase { + + private static final long INITIAL_VALUE = 1; + @Override public String[] getMappings() { return new String[] { "idgen/enhanced/sequence/Pooled.hbm.xml" }; @@ -36,31 +40,47 @@ public void testNormalBoundary() { PooledOptimizer optimizer = (PooledOptimizer) generator.getOptimizer(); int increment = optimizer.getIncrementSize(); - Entity[] entities = new Entity[ increment + 2 ]; - Session s = openSession(); - s.beginTransaction(); - for ( int i = 0; i <= increment; i++ ) { - entities[i] = new Entity( "" + ( i + 1 ) ); - s.save( entities[i] ); - assertEquals( 2, generator.getDatabaseStructure().getTimesAccessed() ); // initialization calls seq twice - assertEquals( increment + 1, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() ); // initialization calls seq twice - assertEquals( i + 1, ( (BasicHolder) optimizer.getLastValue() ).getActualLongValue() ); - assertEquals( increment + 1, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() ); - } - // now force a "clock over" - entities[ increment + 1 ] = new Entity( "" + increment ); - s.save( entities[ increment + 1 ] ); - assertEquals( 3, generator.getDatabaseStructure().getTimesAccessed() ); // initialization (2) + clock over - assertEquals( ( increment * 2 ) + 1, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() ); // initialization (2) + clock over - assertEquals( increment + 2, ( (BasicHolder) optimizer.getLastValue() ).getActualLongValue() ); - s.getTransaction().commit(); - s.beginTransaction(); - for ( int i = 0; i < entities.length; i++ ) { - assertEquals( i + 1, entities[i].getId().intValue() ); - s.delete( entities[i] ); - } - s.getTransaction().commit(); - s.close(); + TransactionUtil.doInHibernate( + this::sessionFactory, + s -> { + // The value that we get from the callback is the high value (PooledOptimizer by default) + // When first increment is initialValue, we can only generate one id from it -> id 1 + Entity entity = new Entity( "" + INITIAL_VALUE ); + s.save( entity ); + + long expectedId = INITIAL_VALUE; + assertEquals( expectedId, entity.getId().longValue() ); + assertEquals( 1, generator.getDatabaseStructure().getTimesAccessed() ); + assertEquals( INITIAL_VALUE, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() ); + assertEquals( INITIAL_VALUE, ( (BasicHolder) optimizer.getLastValue() ).getActualLongValue() ); + assertEquals( INITIAL_VALUE, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() ); + + // now start a full range of values, callback give us hiValue 11 + // id : 2,3,4...,11 + for ( int i = 1; i <= increment; i++ ) { + entity = new Entity( "" + ( i + INITIAL_VALUE ) ); + s.save( entity ); + + expectedId = i + INITIAL_VALUE; + assertEquals( expectedId, entity.getId().longValue() ); + assertEquals( 2, generator.getDatabaseStructure().getTimesAccessed() ); + assertEquals( increment + 1, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() ); + assertEquals( expectedId, ( (BasicHolder) optimizer.getLastValue() ).getActualLongValue() ); + } + + // now force a "clock over" + expectedId++; + entity = new Entity( "" + expectedId ); + s.save( entity ); + + assertEquals( expectedId, entity.getId().longValue() ); + assertEquals( 3, generator.getDatabaseStructure().getTimesAccessed() ); + assertEquals( increment * 2L + 1, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() ); + assertEquals( expectedId, ( (BasicHolder) optimizer.getLastValue() ).getActualLongValue() ); + + s.createQuery( "delete Entity" ).executeUpdate(); + } + ); } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/idgen/enhanced/table/PooledTableTest.java b/hibernate-core/src/test/java/org/hibernate/test/idgen/enhanced/table/PooledTableTest.java index 8575845ddf64..c08051ffeab2 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/idgen/enhanced/table/PooledTableTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/idgen/enhanced/table/PooledTableTest.java @@ -13,6 +13,7 @@ import org.hibernate.id.enhanced.TableGenerator; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.hibernate.testing.transaction.TransactionUtil; import static org.hibernate.id.IdentifierGeneratorHelper.BasicHolder; import static org.hibernate.testing.junit4.ExtraAssertions.assertClassAssignability; @@ -22,6 +23,9 @@ * @author Steve Ebersole */ public class PooledTableTest extends BaseCoreFunctionalTestCase { + + private static final long INITIAL_VALUE = 1; + @Override public String[] getMappings() { return new String[] { "idgen/enhanced/table/Pooled.hbm.xml" }; @@ -36,31 +40,47 @@ public void testNormalBoundary() { PooledOptimizer optimizer = (PooledOptimizer) generator.getOptimizer(); int increment = optimizer.getIncrementSize(); - Entity[] entities = new Entity[ increment + 2 ]; - Session s = openSession(); - s.beginTransaction(); - for ( int i = 0; i <= increment; i++ ) { - entities[i] = new Entity( "" + ( i + 1 ) ); - s.save( entities[i] ); - assertEquals( 2, generator.getTableAccessCount() ); // initialization calls seq twice - assertEquals( increment + 1, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() ); // initialization calls seq twice - assertEquals( i + 1, ( (BasicHolder) optimizer.getLastValue() ).getActualLongValue() ); - assertEquals( increment + 1, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() ); - } - // now force a "clock over" - entities[ increment + 1 ] = new Entity( "" + increment ); - s.save( entities[ increment + 1 ] ); - assertEquals( 3, generator.getTableAccessCount() ); // initialization (2) + clock over - assertEquals( ( increment * 2 ) + 1, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() ); // initialization (2) + clock over - assertEquals( increment + 2, ( (BasicHolder) optimizer.getLastValue() ).getActualLongValue() ); - s.getTransaction().commit(); - s.beginTransaction(); - for ( int i = 0; i < entities.length; i++ ) { - assertEquals( i + 1, entities[i].getId().intValue() ); - s.delete( entities[i] ); - } - s.getTransaction().commit(); - s.close(); + TransactionUtil.doInHibernate( + this::sessionFactory, + s -> { + // The value that we get from the callback is the high value (PooledOptimizer by default) + // When first increment is initialValue, we can only generate one id from it -> id 1 + Entity entity = new Entity( "" + INITIAL_VALUE ); + s.save( entity ); + + long expectedId = INITIAL_VALUE; + assertEquals( expectedId, entity.getId().longValue() ); + assertEquals( 1, generator.getTableAccessCount() ); + assertEquals( INITIAL_VALUE, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() ); + assertEquals( INITIAL_VALUE, ( (BasicHolder) optimizer.getLastValue() ).getActualLongValue() ); + assertEquals( INITIAL_VALUE, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() ); + + // now start a full range of values, callback give us hiValue 11 + // id : 2,3,4...,11 + for ( int i = 1; i <= increment; i++ ) { + entity = new Entity( "" + ( i + INITIAL_VALUE ) ); + s.save( entity ); + + expectedId = i + INITIAL_VALUE; + assertEquals( expectedId, entity.getId().longValue() ); + assertEquals( 2, generator.getTableAccessCount() ); + assertEquals( increment + 1, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() ); + assertEquals( expectedId, ( (BasicHolder) optimizer.getLastValue() ).getActualLongValue() ); + } + + // now force a "clock over" + expectedId++; + entity = new Entity( "" + expectedId ); + s.save( entity ); + + assertEquals( expectedId, entity.getId().longValue() ); + assertEquals( 3, generator.getTableAccessCount() ); + assertEquals( increment * 2L + 1, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() ); + assertEquals( expectedId, ( (BasicHolder) optimizer.getLastValue() ).getActualLongValue() ); + + s.createQuery( "delete Entity" ).executeUpdate(); + } + ); } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/inheritance/JoinedInheritanceDeletionTest.java b/hibernate-core/src/test/java/org/hibernate/test/inheritance/JoinedInheritanceDeletionTest.java new file mode 100644 index 000000000000..43ce1a8b3440 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/inheritance/JoinedInheritanceDeletionTest.java @@ -0,0 +1,124 @@ +package org.hibernate.test.inheritance; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; +import org.hibernate.dialect.PostgreSQL81Dialect; + +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Before; +import org.junit.Test; + +@RequiresDialect(PostgreSQL81Dialect.class) +@TestForIssue( jiraKey = "HHH-15115") +public class JoinedInheritanceDeletionTest extends BaseCoreFunctionalTestCase { + + @Override + protected void configure(Configuration configuration) { + configuration.setProperty( AvailableSettings.DEFAULT_SCHEMA, "public" ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Person.class, + Employee.class, + Customer.class + }; + } + + @Before + public void setUp() { + inTransaction( + session -> { + Person person = new Person( 1, "Bob" ); + Employee employee = new Employee( 2, "Chris", "Software Engineer" ); + Customer customer = new Customer( 3, "Miriam", "" ); + + session.save( person ); + session.save( employee ); + session.save( customer ); + } + ); + } + + @Test + public void testDelete() { + inTransaction( + session -> { + session.createQuery( "delete from Person" ).executeUpdate(); + } + ); + } + + @Entity(name = "Person") + @Inheritance(strategy = InheritanceType.JOINED) + public static class Person { + + @Id + private Integer id; + + private String name; + + public Person() { + } + + public Person(Integer id, String name) { + this.id = id; + this.name = name; + } + + public Integer getId() { + return id; + } + + public String getName() { + return name; + } + + } + + @Entity(name = "Customer") + public static class Customer extends Person { + + private String comments; + + public Customer() { + } + + public Customer(Integer id, String name, String comments) { + super( id, name ); + this.comments = comments; + } + + public String getComments() { + return comments; + } + + } + + @Entity(name = "Employee") + public static class Employee extends Person { + + private String title; + + public Employee() { + } + + public Employee(Integer id, String name, String title) { + super( id, name ); + this.title = title; + } + + public String getTitle() { + return title; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/jdbc/env/NoDatabaseMetaDataTest.java b/hibernate-core/src/test/java/org/hibernate/test/jdbc/env/NoDatabaseMetaDataTest.java index dbe88a7c4eaa..778e6a6140f7 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/jdbc/env/NoDatabaseMetaDataTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/jdbc/env/NoDatabaseMetaDataTest.java @@ -42,7 +42,7 @@ public void testNoJdbcMetadataDefaultDialect() { assertFalse( extractedDatabaseMetaData.supportsRefCursors() ); assertFalse( extractedDatabaseMetaData.supportsScrollableResults() ); assertFalse( extractedDatabaseMetaData.supportsGetGeneratedKeys() ); - assertFalse( extractedDatabaseMetaData.supportsBatchUpdates() ); + assertTrue( extractedDatabaseMetaData.supportsBatchUpdates() ); assertFalse( extractedDatabaseMetaData.supportsDataDefinitionInTransaction() ); assertFalse( extractedDatabaseMetaData.doesDataDefinitionCauseTransactionCommit() ); assertNull( extractedDatabaseMetaData.getSqlStateType() ); @@ -66,7 +66,7 @@ public void testNoJdbcMetadataDialectOverride() { assertFalse( extractedDatabaseMetaData.supportsRefCursors() ); assertFalse( extractedDatabaseMetaData.supportsScrollableResults() ); assertFalse( extractedDatabaseMetaData.supportsGetGeneratedKeys() ); - assertFalse( extractedDatabaseMetaData.supportsBatchUpdates() ); + assertTrue( extractedDatabaseMetaData.supportsBatchUpdates() ); assertFalse( extractedDatabaseMetaData.supportsDataDefinitionInTransaction() ); assertFalse( extractedDatabaseMetaData.doesDataDefinitionCauseTransactionCommit() ); assertNull( extractedDatabaseMetaData.getSqlStateType() ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/jpa/convert/UUIDConverterTest.java b/hibernate-core/src/test/java/org/hibernate/test/jpa/convert/UUIDConverterTest.java new file mode 100644 index 000000000000..64f55753e4d0 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/jpa/convert/UUIDConverterTest.java @@ -0,0 +1,104 @@ +/* + * 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.jpa.convert; + +import java.util.UUID; +import javax.persistence.AttributeConverter; +import javax.persistence.Convert; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +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; + +/** + * @author Christian Beikov + */ +public class UUIDConverterTest extends BaseEntityManagerFunctionalTestCase { + + private UUID uuid = UUID.randomUUID(); + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { TestEntity.class }; + } + + @Test + @TestForIssue(jiraKey = "HHH-15097") + public void testSqlTypeDescriptorForConverted() { + // persist the record. + Integer rowId = doInJPA( this::entityManagerFactory, entityManager -> { + TestEntity e = new TestEntity(); + e.setSomeValue( new SomeValue( uuid = UUID.randomUUID() ) ); + entityManager.persist( e ); + return e.getId(); + } ); + + // retrieve the record and verify values. + doInJPA( this::entityManagerFactory, entityManager -> { + final TestEntity e = entityManager.find( TestEntity.class, rowId ); + assertEquals( uuid, e.getSomeValue().uuid ); + } ); + } + + @Entity(name = "TestEntity") + public static class TestEntity { + @Id + @GeneratedValue + private Integer id; + + @Convert(converter = UUIDConverter.class) + private SomeValue someValue; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public SomeValue getSomeValue() { + return someValue; + } + + public void setSomeValue(SomeValue someValue) { + this.someValue = someValue; + } + + } + + public static class UUIDConverter implements AttributeConverter { + @Override + public UUID convertToDatabaseColumn(SomeValue attribute) { + return attribute == null ? null : attribute.uuid; + } + + @Override + public SomeValue convertToEntityAttribute(UUID dbData) { + return dbData == null ? null : new SomeValue( dbData ); + } + } + + public static class SomeValue { + private final UUID uuid; + + public SomeValue(UUID uuid) { + this.uuid = uuid; + } + + public UUID getUuid() { + return uuid; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/jpa/ql/MapKeySubquerySchemaTest.java b/hibernate-core/src/test/java/org/hibernate/test/jpa/ql/MapKeySubquerySchemaTest.java new file mode 100644 index 000000000000..18df486657f2 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/jpa/ql/MapKeySubquerySchemaTest.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.test.jpa.ql; + +import java.util.Properties; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; +import org.hibernate.dialect.H2Dialect; +import org.hibernate.persister.collection.QueryableCollection; + +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.hibernate.test.jpa.MapContent; +import org.hibernate.test.jpa.MapOwner; +import org.hibernate.test.jpa.Relationship; +import org.junit.Assert; +import org.junit.Test; + +@RequiresDialect(H2Dialect.class) +public class MapKeySubquerySchemaTest extends BaseCoreFunctionalTestCase { + + private static final String CUSTOM_SCHEMA = "CUSTOM_SCHEMA"; + + @Test + @TestForIssue( jiraKey = "HHH-15523") + public void testMapKeyLoad() { + final QueryableCollection collectionPersister = (QueryableCollection) sessionFactory().getMetamodel() + .collectionPersister( MapOwner.class.getName() + ".contents" ); + Assert.assertTrue( + "Index SQL does not contain the schema name", + collectionPersister.getIndexFormulas()[0].contains( CUSTOM_SCHEMA + ".MapContent " ) + + ); + } + + @Override + protected void configure(Configuration configuration) { + final Properties properties = new Properties(); + properties.put( AvailableSettings.DEFAULT_SCHEMA, CUSTOM_SCHEMA ); + configuration.addProperties( properties ); + } + + @Override + protected String createSecondSchema() { + return CUSTOM_SCHEMA; + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { MapOwner.class, MapContent.class, Relationship.class}; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/limit/Oracle12LimitTest.java b/hibernate-core/src/test/java/org/hibernate/test/limit/Oracle12LimitTest.java new file mode 100644 index 000000000000..6d67b5619952 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/limit/Oracle12LimitTest.java @@ -0,0 +1,90 @@ +package org.hibernate.test.limit; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToMany; +import javax.persistence.Table; +import javax.persistence.TypedQuery; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Join; +import javax.persistence.criteria.JoinType; +import javax.persistence.criteria.Predicate; +import javax.persistence.criteria.Root; + +import org.hibernate.dialect.Oracle8iDialect; + +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +@RequiresDialect(Oracle8iDialect.class) +@TestForIssue(jiraKey = "HHH-14819") +public class Oracle12LimitTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Person.class, + UserFunctionalArea.class + }; + } + + @Test + public void testLimit() { + inTransaction( + session -> { + final CriteriaBuilder criteriabuilder = session.getCriteriaBuilder(); + final CriteriaQuery criteriaquery = criteriabuilder.createQuery(); + final Root personRoot = criteriaquery.from( Person.class ); + final Join functionalArea = personRoot.join( + "functionalArea", + JoinType.LEFT + ); + + List predicates = new ArrayList<>(); + predicates.add( criteriabuilder.or( criteriabuilder.equal( personRoot.get( "name" ), "A" ) ) ); + + List notNullPredicate = predicates.parallelStream().filter( Objects::nonNull ) + .collect( Collectors.toList() ); + criteriaquery.select( personRoot ).where( notNullPredicate.toArray( new Predicate[] {} ) ).distinct( + true ); + criteriaquery.orderBy( criteriabuilder.desc( criteriabuilder.upper( functionalArea.get( + "userAreaName" ) ) ) ); + + final TypedQuery createQuery = session.createQuery( criteriaquery ); + createQuery.setFirstResult( 0 ).setMaxResults( 10 ).getResultList(); + } + ); + } + + @Entity(name = "Person") + @Table(name = "Person") + public static class Person { + @Id + private Long id; + + @OneToMany +// @JoinColumn(name = "USER_KEY", referencedColumnName = "USER_KEY") + private List functionalArea; + + private String name; + } + + @Entity(name = "UserFunctionalArea") + @Table(name = "UserFunctionalArea") + public static class UserFunctionalArea { + @Id + @Column(name = "USER_KEY") + private Integer id; + + private String userAreaName; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/lob/BlobLocatorTest.java b/hibernate-core/src/test/java/org/hibernate/test/lob/BlobLocatorTest.java index 7242d1ead7a4..d5b98f76c456 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/lob/BlobLocatorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/lob/BlobLocatorTest.java @@ -12,6 +12,7 @@ import org.hibernate.Hibernate; import org.hibernate.LockOptions; import org.hibernate.Session; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.dialect.TeradataDialect; import org.hibernate.testing.DialectChecks; @@ -45,6 +46,7 @@ public String[] getMappings() { jiraKey = "HHH-6637", comment = "Teradata requires locator to be used in same session where it was created/retrieved" ) + @SkipForDialect( value = SybaseASE15Dialect.class, comment = "Sybase doesn't support empty blobs") public void testBoundedBlobLocatorAccess() throws Throwable { byte[] original = buildByteArray( BLOB_SIZE, true ); byte[] changed = buildByteArray( BLOB_SIZE, false ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/lob/ClobLocatorTest.java b/hibernate-core/src/test/java/org/hibernate/test/lob/ClobLocatorTest.java index 1aa1df12ec42..428161ffde6f 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/lob/ClobLocatorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/lob/ClobLocatorTest.java @@ -11,6 +11,7 @@ import org.hibernate.LockOptions; import org.hibernate.Session; import org.hibernate.dialect.SybaseASE157Dialect; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.dialect.TeradataDialect; import org.hibernate.type.descriptor.java.DataHelper; @@ -47,6 +48,7 @@ public String[] getMappings() { jiraKey = "HHH-6637", comment = "Teradata requires locator to be used in same session where it was created/retrieved" ) + @SkipForDialect( value = SybaseASE15Dialect.class, comment = "jTDS driver doesn't implement character stream handling") public void testBoundedClobLocatorAccess() throws Throwable { String original = buildString( CLOB_SIZE, 'x' ); String changed = buildString( CLOB_SIZE, 'y' ); @@ -131,6 +133,7 @@ public void testBoundedClobLocatorAccess() throws Throwable { value = DialectChecks.SupportsUnboundedLobLocatorMaterializationCheck.class, comment = "database/driver does not support materializing a LOB locator outside the owning transaction" ) + @SkipForDialect( value = SybaseASE15Dialect.class, comment = "jTDS driver doesn't implement character stream handling") public void testUnboundedClobLocatorAccess() throws Throwable { // Note: unbounded mutation of the underlying lob data is completely // unsupported; most databases would not allow such a construct anyway. diff --git a/hibernate-core/src/test/java/org/hibernate/test/lob/LongByteArrayTest.java b/hibernate-core/src/test/java/org/hibernate/test/lob/LongByteArrayTest.java index f35708f864e3..138c448f42fe 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/lob/LongByteArrayTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/lob/LongByteArrayTest.java @@ -9,7 +9,9 @@ import java.util.Arrays; import org.hibernate.Session; +import org.hibernate.dialect.SybaseASE15Dialect; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.Assert; import org.junit.Test; @@ -26,6 +28,7 @@ public abstract class LongByteArrayTest extends BaseCoreFunctionalTestCase { private static final int ARRAY_SIZE = 10000; @Test + @SkipForDialect( value = SybaseASE15Dialect.class, comment = "Sybase doesn't support empty blobs") public void testBoundedLongByteArrayAccess() { byte[] original = buildRecursively( ARRAY_SIZE, true ); byte[] changed = buildRecursively( ARRAY_SIZE, false ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/lob/MaterializedClobTest.java b/hibernate-core/src/test/java/org/hibernate/test/lob/MaterializedClobTest.java index a2670a2fbc3d..ab39d463abe0 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/lob/MaterializedClobTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/lob/MaterializedClobTest.java @@ -6,8 +6,11 @@ */ package org.hibernate.test.lob; +import org.hibernate.dialect.SybaseASE15Dialect; + import org.hibernate.testing.DialectChecks; import org.hibernate.testing.RequiresDialectFeature; +import org.hibernate.testing.SkipForDialect; /** * Tests eager materialization and mutation of data mapped by @@ -16,6 +19,7 @@ * @author Gail Badner */ @RequiresDialectFeature( DialectChecks.SupportsExpectedLobUsagePattern.class ) +@SkipForDialect( value = SybaseASE15Dialect.class, comment = "jTDS driver doesn't implement character stream handling") public class MaterializedClobTest extends LongStringTest { public String[] getMappings() { return new String[] { "lob/MaterializedClobMappings.hbm.xml" }; diff --git a/hibernate-core/src/test/java/org/hibernate/test/locking/paging/PagingAndLockingTest.java b/hibernate-core/src/test/java/org/hibernate/test/locking/paging/PagingAndLockingTest.java index d29bf5a3f311..3c55805fe244 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/locking/paging/PagingAndLockingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/locking/paging/PagingAndLockingTest.java @@ -13,7 +13,9 @@ import org.hibernate.Query; import org.hibernate.SQLQuery; import org.hibernate.Session; +import org.hibernate.dialect.SybaseASE15Dialect; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.After; @@ -28,6 +30,7 @@ * @author Steve Ebersole */ @TestForIssue( jiraKey = "HHH-1168" ) +@SkipForDialect( value = SybaseASE15Dialect.class, comment = "Didn't dig into it too deeply, but I think it requires follow on locking") public class PagingAndLockingTest extends BaseCoreFunctionalTestCase { @Override protected Class[] getAnnotatedClasses() { diff --git a/hibernate-core/src/test/java/org/hibernate/test/manytomanyassociationclass/surrogateid/generated/ManyToManyAssociationClassGeneratedIdTest.java b/hibernate-core/src/test/java/org/hibernate/test/manytomanyassociationclass/surrogateid/generated/ManyToManyAssociationClassGeneratedIdTest.java index c3ab40350f69..96d1e3c7721d 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/manytomanyassociationclass/surrogateid/generated/ManyToManyAssociationClassGeneratedIdTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/manytomanyassociationclass/surrogateid/generated/ManyToManyAssociationClassGeneratedIdTest.java @@ -10,8 +10,10 @@ import java.util.HashSet; import org.hibernate.Session; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.exception.ConstraintViolationException; +import org.hibernate.testing.SkipForDialect; import org.hibernate.test.manytomanyassociationclass.AbstractManyToManyAssociationClassTest; import org.hibernate.test.manytomanyassociationclass.Membership; import org.junit.Test; @@ -24,6 +26,7 @@ * * @author Gail Badner */ +@SkipForDialect(value = SybaseASE15Dialect.class, comment = "Missing constraint violation extractor") public class ManyToManyAssociationClassGeneratedIdTest extends AbstractManyToManyAssociationClassTest { @Override public String[] getMappings() { diff --git a/hibernate-core/src/test/java/org/hibernate/test/mapping/usertypes/UserTypeMappingTest.java b/hibernate-core/src/test/java/org/hibernate/test/mapping/usertypes/UserTypeMappingTest.java index bae30b67ddf9..3a871fbfd139 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/mapping/usertypes/UserTypeMappingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/mapping/usertypes/UserTypeMappingTest.java @@ -34,13 +34,6 @@ public class UserTypeMappingTest extends BaseUnitTestCase{ @Before public void setup(){ cfg=new Configuration(); - Properties p = new Properties(); - p.put( Environment.DIALECT, "org.hibernate.dialect.HSQLDialect" ); - p.put( "hibernate.connection.driver_class", "org.h2.Driver" ); - p.put( "hibernate.connection.url", "jdbc:h2:mem:" ); - p.put( "hibernate.connection.username", "sa" ); - p.put( "hibernate.connection.password", "" ); - cfg.setProperties(p); serviceRegistry = ServiceRegistryBuilder.buildServiceRegistry( cfg.getProperties() ); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/onetoone/cache/OneToOneCacheTest.java b/hibernate-core/src/test/java/org/hibernate/test/onetoone/cache/OneToOneCacheTest.java index d14e6c56a442..0c09734230c7 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/onetoone/cache/OneToOneCacheTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/onetoone/cache/OneToOneCacheTest.java @@ -14,6 +14,8 @@ import org.hibernate.cfg.Configuration; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.stat.spi.StatisticsImplementor; + +import org.hibernate.testing.FailureExpected; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.Test; @@ -109,6 +111,7 @@ private List getPersons(Class personC } @Test + @FailureExpected( jiraKey = "HHH-14216", message = "The changes introduces by HHH-14216 have been reverted see https://github.com/hibernate/hibernate-orm/pull/5061 discussion") public void OneToOneCacheByForeignKey() throws Exception { OneToOneTest(PersonByFK.class, DetailsByFK.class); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/onetoone/flush/DirtyFlushTest.java b/hibernate-core/src/test/java/org/hibernate/test/onetoone/flush/DirtyFlushTest.java new file mode 100644 index 000000000000..4806719f0b5f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/onetoone/flush/DirtyFlushTest.java @@ -0,0 +1,109 @@ +/* + * 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.onetoone.flush; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.OneToOne; +import javax.persistence.Table; +import javax.persistence.Version; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.TestForIssue; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.orm.transaction.TransactionUtil.inTransaction; +import static org.junit.Assert.assertEquals; + +@TestForIssue(jiraKey = "HHH-15045") +public class DirtyFlushTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { User.class, Profile.class }; + } + + @Before + public void setUp() { + inTransaction( getOrCreateEntityManager(), em -> { + final User user = new User(); + user.id = 1; + user.version = 1; + + final Profile profile = new Profile(); + profile.id = 1; + profile.version = 1; + + em.persist( user ); + em.persist( profile ); + } ); + } + + @Test + public void testDirtyFlushNotHappened() { + inTransaction( getOrCreateEntityManager(), em -> { + final User user = em.find( User.class, 1 ); + assertEquals( 1, user.version ); + + final Profile profile = em.find( Profile.class, 1 ); + assertEquals( 1, profile.version ); + + profile.user = user; + user.profile = profile; + + em.persist( profile ); + } ); + + inTransaction( getOrCreateEntityManager(), em -> { + final Profile profile = em.find( Profile.class, 1 ); + assertEquals( 2, profile.version ); + + final User user = em.find( User.class, 1 ); + assertEquals( + "without fixing, the version will be bumped due to erroneous dirty flushing", + 1, + user.version + ); + } ); + } + + @After + public void tearDown() { + inTransaction( getOrCreateEntityManager(), em -> { + em.createQuery( "delete from Profile" ).executeUpdate(); + em.createQuery( "delete from User" ).executeUpdate(); + } ); + } + + @Entity(name = "User") + @Table(name = "USER_TABLE") + public static class User { + @Id + int id; + + @Version + int version; + + @OneToOne(mappedBy = "user") + Profile profile; + } + + @Entity(name = "Profile") + @Table(name = "PROFILE_TABLE") + public static class Profile { + @Id + int id; + + @Version + int version; + @OneToOne // internally Hibernate will use `@ManyToOne` for this field + User user; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/ops/CreateTest.java b/hibernate-core/src/test/java/org/hibernate/test/ops/CreateTest.java index b992eaae5122..99076be00dcc 100755 --- a/hibernate-core/src/test/java/org/hibernate/test/ops/CreateTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/ops/CreateTest.java @@ -14,6 +14,7 @@ import org.hibernate.Session; import org.hibernate.Transaction; import org.hibernate.dialect.AbstractHANADialect; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.exception.ConstraintViolationException; import org.hibernate.testing.DialectChecks; @@ -120,6 +121,7 @@ public void testCreateTreeWithGeneratedId() { } @Test + @SkipForDialect(value = SybaseASE15Dialect.class, comment = "Missing constraint violation extractor") public void testCreateException() { Session s = openSession(); Transaction tx = s.beginTransaction(); diff --git a/hibernate-core/src/test/java/org/hibernate/test/procedure/StoredProcedureParameterTypeTest.java b/hibernate-core/src/test/java/org/hibernate/test/procedure/StoredProcedureParameterTypeTest.java index d02c2d54673d..9a209bf4df21 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/procedure/StoredProcedureParameterTypeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/procedure/StoredProcedureParameterTypeTest.java @@ -29,6 +29,7 @@ import javax.sql.rowset.serial.SerialBlob; import javax.sql.rowset.serial.SerialClob; +import org.hibernate.jpa.TypedParameterValue; import org.hibernate.procedure.ProcedureCall; import org.hibernate.type.BigDecimalType; import org.hibernate.type.BigIntegerType; @@ -71,6 +72,7 @@ /** * @author Vlad Mihalcea + * @author Yanming Zhou */ public class StoredProcedureParameterTypeTest extends BaseNonConfigCoreFunctionalTestCase { @@ -422,4 +424,49 @@ public void testStringTypeInParameterIsNullWithoutEnablePassingNulls() { } ); } + + @Test + @TestForIssue(jiraKey = "HHH-15618") + public void testTypedParameterValueInParameter() { + inTransaction( + session -> { + ProcedureCall procedureCall = session.createStoredProcedureCall( "test" ); + procedureCall.registerParameter( 1, StringType.class, ParameterMode.IN ); + procedureCall.setParameter( 1, new TypedParameterValue( StringType.INSTANCE, "test" ) ); + + procedureCall = session.createStoredProcedureCall( "test" ); + procedureCall.registerParameter( "test", StringType.class, ParameterMode.IN ); + procedureCall.setParameter( "test", new TypedParameterValue( StringType.INSTANCE, "test" ) ); + } + ); + } + + @Test + @TestForIssue(jiraKey = "HHH-15618") + public void testTypedParameterValueInParameterWithEnablePassingNulls() { + inTransaction( + session -> { + ProcedureCall procedureCall = session.createStoredProcedureCall( "test" ); + procedureCall.registerParameter( 1, StringType.class, ParameterMode.IN ).enablePassingNulls( true ); + procedureCall.setParameter( 1, new TypedParameterValue( StringType.INSTANCE, null ) ); + } + ); + } + + @Test + @TestForIssue(jiraKey = "HHH-15618") + public void testTypedParameterValueInParameterWithNotSpecifiedType() { + inTransaction( + session -> { + try { + ProcedureCall procedureCall = session.createStoredProcedureCall( "test" ); + procedureCall.registerParameter( 1, StringType.class, ParameterMode.IN ); + procedureCall.setParameter( 1, new TypedParameterValue( IntegerType.INSTANCE, 1 ) ); + } + catch (IllegalArgumentException e) { + assertTrue( e.getMessage().contains( "was not of specified type" ) ); + } + } + ); + } } \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/proxy/ProxyTest.java b/hibernate-core/src/test/java/org/hibernate/test/proxy/ProxyTest.java index c03dc7330ae8..7d8aedcd4e8c 100755 --- a/hibernate-core/src/test/java/org/hibernate/test/proxy/ProxyTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/proxy/ProxyTest.java @@ -103,6 +103,60 @@ public void testProxyException() { s.close(); } + @Test + public void testProxyExceptionWithNewGetReference() { + Session s = openSession(); + Transaction t = s.beginTransaction(); + DataPoint dp = new DataPoint(); + dp.setDescription("a data point"); + dp.setX( new BigDecimal("1.0") ); + dp.setY( new BigDecimal("2.0") ); + s.persist(dp); + s.flush(); + s.clear(); + + dp = s.getReference(dp); + assertFalse( Hibernate.isInitialized(dp) ); + + try { + dp.exception(); + fail(); + } + catch (Exception e) { + assertTrue( e.getClass()==Exception.class ); + } + s.delete(dp); + t.commit(); + s.close(); + } + + @Test + public void testProxyExceptionWithOldGetReference() { + Session s = openSession(); + Transaction t = s.beginTransaction(); + DataPoint dp = new DataPoint(); + dp.setDescription("a data point"); + dp.setX( new BigDecimal(1.0) ); + dp.setY( new BigDecimal(2.0) ); + s.persist(dp); + s.flush(); + s.clear(); + + dp = s.getReference(DataPoint.class, new Long( dp.getId() ) ); + assertFalse( Hibernate.isInitialized(dp) ); + + try { + dp.exception(); + fail(); + } + catch (Exception e) { + assertTrue( e.getClass()==Exception.class ); + } + s.delete(dp); + t.commit(); + s.close(); + } + @Test public void testProxySerializationAfterSessionClosed() { Session s = openSession(); @@ -243,7 +297,57 @@ public void testProxy() { assertTrue( Hibernate.isInitialized(dp) ); s.clear(); - dp = (DataPoint) s.load( DataPoint.class, new Long ( dp.getId() ) ); + dp = s.load( DataPoint.class, dp.getId()); + assertFalse( Hibernate.isInitialized(dp) ); + dp2 = (DataPoint) s.createQuery("from DataPoint").uniqueResult(); + assertSame(dp, dp2); + assertTrue( Hibernate.isInitialized(dp) ); + s.delete( dp ); + t.commit(); + s.close(); + } + + @Test + public void testProxyWithGetReference() { + Session s = openSession(); + Transaction t = s.beginTransaction(); + DataPoint dp = new DataPoint(); + dp.setDescription("a data point"); + dp.setX( new BigDecimal("1.0") ); + dp.setY( new BigDecimal("2.0") ); + s.persist(dp); + s.flush(); + s.clear(); + + dp = s.getReference( DataPoint.class, dp.getId() ); + assertFalse( Hibernate.isInitialized(dp) ); + DataPoint dp2 = s.get( DataPoint.class, dp.getId() ); + assertSame(dp, dp2); + assertTrue( Hibernate.isInitialized(dp) ); + s.clear(); + + dp = s.getReference( DataPoint.class, dp.getId() ); + assertFalse( Hibernate.isInitialized(dp) ); + dp2 = s.getReference( DataPoint.class, dp.getId() ); + assertSame(dp, dp2); + assertFalse( Hibernate.isInitialized(dp) ); + s.clear(); + + dp = s.getReference( dp ); + assertFalse( Hibernate.isInitialized(dp) ); + dp2 = s.getReference( dp ); + assertSame(dp, dp2); + assertFalse( Hibernate.isInitialized(dp) ); + s.clear(); + + dp = s.getReference( DataPoint.class, dp.getId() ); + assertFalse( Hibernate.isInitialized(dp) ); + dp2 = s.byId( DataPoint.class ).with( LockOptions.READ ).load( dp.getId() ); + assertSame(dp, dp2); + assertTrue( Hibernate.isInitialized(dp) ); + s.clear(); + + dp = s.getReference( DataPoint.class, dp.getId() ); assertFalse( Hibernate.isInitialized(dp) ); dp2 = (DataPoint) s.createQuery("from DataPoint").uniqueResult(); assertSame(dp, dp2); diff --git a/hibernate-core/src/test/java/org/hibernate/test/querycache/QueryCacheTest.java b/hibernate-core/src/test/java/org/hibernate/test/querycache/QueryCacheTest.java index 8e23e2cd2f58..27ea000b1fb2 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/querycache/QueryCacheTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/querycache/QueryCacheTest.java @@ -368,15 +368,11 @@ public void testQueryCacheInvalidation() throws Exception { } @Test - @RequiresDialectFeature( - value = DialectChecks.CaseSensitiveCheck.class, - comment = "i.name='widget' should not match on case sensitive database." - ) - public void testCaseInsensitiveComparison() { + public void testComparison() { Session s = openSession(); s.beginTransaction(); Item i = new Item(); - i.setName( "Widget" ); + i.setName( "widget" ); i.setDescription( "A really top-quality, full-featured widget." ); s.save( i ); s.getTransaction().commit(); @@ -387,7 +383,7 @@ public void testCaseInsensitiveComparison() { List result = s.createQuery( queryString ).list(); assertEquals(1, result.size()); i = (Item) s.get( Item.class, new Long(i.getId()) ); - assertEquals( i.getName(), "Widget" ); + assertEquals( i.getName(), "widget" ); s.delete(i); s.getTransaction().commit(); s.close(); diff --git a/hibernate-core/src/test/java/org/hibernate/test/reattachment/Mappings.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/reattachment/Mappings.hbm.xml index 86aee42aa5c3..7e1e6d76bb47 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/reattachment/Mappings.hbm.xml +++ b/hibernate-core/src/test/java/org/hibernate/test/reattachment/Mappings.hbm.xml @@ -22,7 +22,7 @@ - + diff --git a/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SchemaUpdateWithUseJdbcMetadataDefaultsSettingToFalseAndQuotedNameTest.java b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SchemaUpdateWithUseJdbcMetadataDefaultsSettingToFalseAndQuotedNameTest.java index a41cf2003609..d03593f57b40 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SchemaUpdateWithUseJdbcMetadataDefaultsSettingToFalseAndQuotedNameTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SchemaUpdateWithUseJdbcMetadataDefaultsSettingToFalseAndQuotedNameTest.java @@ -22,14 +22,17 @@ import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.cfg.AvailableSettings; import org.hibernate.dialect.Dialect; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.tool.hbm2ddl.SchemaExport; import org.hibernate.tool.hbm2ddl.SchemaUpdate; import org.hibernate.tool.hbm2ddl.SchemaValidator; import org.hibernate.tool.schema.JdbcMetadaAccessStrategy; import org.hibernate.tool.schema.TargetType; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import org.junit.After; +import org.junit.Assume; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -81,6 +84,10 @@ public void setUp() throws IOException { @After public void tearDown() { + // The jTDS driver doesn't support schemas + if ( metadata.getDatabase().getDialect() instanceof SybaseASE15Dialect ) { + return; + } new SchemaExport().setHaltOnError( true ) .setFormat( false ) .drop( EnumSet.of( TargetType.DATABASE ), metadata ); @@ -88,8 +95,13 @@ public void tearDown() { } @Test +// @SkipForDialect(value = SybaseASE15Dialect.class, comment = "The jTDS driver doesn't support schemas") public void testSchemaUpdateDoesNotTryToRecreateExistingTables() throws Exception { + // The jTDS driver doesn't support schemas + if ( metadata.getDatabase().getDialect() instanceof SybaseASE15Dialect ) { + return; + } createSchema(); new SchemaUpdate().setHaltOnError( true ) diff --git a/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SchemaUpdateWithUseJdbcMetadataDefaultsSettingToFalseTest.java b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SchemaUpdateWithUseJdbcMetadataDefaultsSettingToFalseTest.java index 3d3eedf77710..9b30637fa54f 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SchemaUpdateWithUseJdbcMetadataDefaultsSettingToFalseTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SchemaUpdateWithUseJdbcMetadataDefaultsSettingToFalseTest.java @@ -21,14 +21,17 @@ import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.tool.hbm2ddl.SchemaExport; import org.hibernate.tool.hbm2ddl.SchemaUpdate; import org.hibernate.tool.hbm2ddl.SchemaValidator; import org.hibernate.tool.schema.JdbcMetadaAccessStrategy; import org.hibernate.tool.schema.TargetType; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import org.junit.After; +import org.junit.Assume; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -84,6 +87,10 @@ public void setUp() throws IOException { @After public void tearDown() { + // The jTDS driver doesn't support schemas + if ( metadata.getDatabase().getDialect() instanceof SybaseASE15Dialect ) { + return; + } new SchemaExport().setHaltOnError( true ) .setFormat( false ) .drop( EnumSet.of( TargetType.DATABASE ), metadata ); @@ -91,8 +98,13 @@ public void tearDown() { } @Test +// @SkipForDialect(value = SybaseASE15Dialect.class, comment = "The jTDS driver doesn't support schemas") public void testSchemaUpdateDoesNotTryToRecreateExistingTables() throws Exception { + // The jTDS driver doesn't support schemas + if ( metadata.getDatabase().getDialect() instanceof SybaseASE15Dialect ) { + return; + } createSchema(); new SchemaUpdate().setHaltOnError( true ) diff --git a/hibernate-core/src/test/java/org/hibernate/test/schemavalidation/IdentityGenerationValidationTest.java b/hibernate-core/src/test/java/org/hibernate/test/schemavalidation/IdentityGenerationValidationTest.java new file mode 100644 index 000000000000..fce4f3bb1ba6 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/schemavalidation/IdentityGenerationValidationTest.java @@ -0,0 +1,63 @@ +/* + * 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.schemavalidation; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.dialect.PostgreSQL10Dialect; +import org.hibernate.tool.hbm2ddl.SchemaValidator; + +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.RequiresDialects; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +/** + * @author Jan Schatteman + */ +@RequiresDialects({ + @RequiresDialect(PostgreSQL10Dialect.class) +}) +@TestForIssue( jiraKey = "HHH-13106" ) +public class IdentityGenerationValidationTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] {TestEntity.class}; + } + + @Test + public void testSynonymUsingIndividuallySchemaValidator() { + StandardServiceRegistry ssr = new StandardServiceRegistryBuilder().build(); + try { + final MetadataSources metadataSources = new MetadataSources( ssr ); + metadataSources.addAnnotatedClass( TestEntity.class ); + + new SchemaValidator().validate( metadataSources.buildMetadata() ); + } + finally { + StandardServiceRegistryBuilder.destroy( ssr ); + } + } + + @Entity + @Table(name = "test_entity") + private static class TestEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/schemavalidation/SynonymValidationTest.java b/hibernate-core/src/test/java/org/hibernate/test/schemavalidation/SynonymValidationTest.java index ceae167933f9..20ba019b6898 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/schemavalidation/SynonymValidationTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/schemavalidation/SynonymValidationTest.java @@ -16,11 +16,13 @@ import org.hibernate.boot.registry.StandardServiceRegistry; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.DB297Dialect; import org.hibernate.dialect.Oracle9iDialect; import org.hibernate.tool.hbm2ddl.SchemaValidator; import org.hibernate.tool.schema.JdbcMetadaAccessStrategy; import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.RequiresDialects; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; import org.hibernate.testing.transaction.TransactionUtil; @@ -28,8 +30,6 @@ import org.junit.Before; import org.junit.Test; -import static org.junit.Assert.assertEquals; - /** * Allows the BaseCoreFunctionalTestCase to create the schema using TestEntity. The test method validates against an * identical entity, but using the synonym name. @@ -39,7 +39,11 @@ * * @author Brett Meyer */ -@RequiresDialect(Oracle9iDialect.class) +@RequiresDialects({ + @RequiresDialect(Oracle9iDialect.class), + @RequiresDialect(DB297Dialect.class) + +}) public class SynonymValidationTest extends BaseNonConfigCoreFunctionalTestCase { private StandardServiceRegistry ssr; @@ -51,14 +55,29 @@ protected Class[] getAnnotatedClasses() { @Before public void setUp() { TransactionUtil.doInHibernate( this::sessionFactory, session -> { - session.createSQLQuery( "CREATE SYNONYM test_synonym FOR test_entity" ).executeUpdate(); + final String createStatement; + if ( getDialect() instanceof Oracle9iDialect ) { + createStatement = "CREATE SYNONYM test_synonym FOR test_entity"; + } + else { + createStatement = "CREATE ALIAS test_synonym FOR test_entity"; + } + session.createNativeQuery( createStatement ).executeUpdate(); + } ); } @After public void tearDown() { TransactionUtil.doInHibernate( this::sessionFactory, session -> { - session.createSQLQuery( "DROP SYNONYM test_synonym FORCE" ).executeUpdate(); + final String dropStatement; + if ( getDialect() instanceof Oracle9iDialect ) { + dropStatement = "DROP SYNONYM test_synonym FORCE"; + } + else { + dropStatement = "DROP ALIAS test_synonym FOR TABLE"; + } + session.createNativeQuery( dropStatement ).executeUpdate(); }); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/sql/autodiscovery/AutoDiscoveryTest.java b/hibernate-core/src/test/java/org/hibernate/test/sql/autodiscovery/AutoDiscoveryTest.java index a7d901699ae2..57bbedda03ee 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/sql/autodiscovery/AutoDiscoveryTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/sql/autodiscovery/AutoDiscoveryTest.java @@ -33,7 +33,7 @@ */ public class AutoDiscoveryTest extends BaseCoreFunctionalTestCase { private static final String QUERY_STRING = - "select u.name as username, g.name as groupname, m.joindate " + + "select u.name as username, g.name as groupname, m.joinDate " + "from t_membership m " + " inner join t_user u on m.member_id = u.id " + " inner join t_group g on m.group_id = g.id"; diff --git a/hibernate-core/src/test/java/org/hibernate/test/sql/hand/custom/sqlserver/Mappings.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/sql/hand/custom/sqlserver/Mappings.hbm.xml index 0407a4125862..52928b26b2e3 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/sql/hand/custom/sqlserver/Mappings.hbm.xml +++ b/hibernate-core/src/test/java/org/hibernate/test/sql/hand/custom/sqlserver/Mappings.hbm.xml @@ -70,11 +70,11 @@ DELETE FROM EMPLOYMENT WHERE EMPID=? - - + + - + INSERT INTO TEXTHOLDER @@ -85,11 +85,11 @@ DELETE FROM TEXTHOLDER WHERE ID=? - - + + - + INSERT INTO IMAGEHOLDER diff --git a/hibernate-core/src/test/java/org/hibernate/test/sql/hand/custom/sybase/Mappings.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/sql/hand/custom/sybase/Mappings.hbm.xml index fd0aca780f26..a2b798274a33 100755 --- a/hibernate-core/src/test/java/org/hibernate/test/sql/hand/custom/sybase/Mappings.hbm.xml +++ b/hibernate-core/src/test/java/org/hibernate/test/sql/hand/custom/sybase/Mappings.hbm.xml @@ -70,11 +70,11 @@ DELETE FROM EMPLOYMENT WHERE EMPID=? - - + + - + INSERT INTO TEXTHOLDER @@ -85,11 +85,11 @@ DELETE FROM TEXTHOLDER WHERE ID=? - - + + - + INSERT INTO IMAGEHOLDER diff --git a/hibernate-core/src/test/java/org/hibernate/test/sql/hand/custom/sybase/SybaseCustomSQLTest.java b/hibernate-core/src/test/java/org/hibernate/test/sql/hand/custom/sybase/SybaseCustomSQLTest.java index 1e11fc84e53e..5949a9ae6cab 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/sql/hand/custom/sybase/SybaseCustomSQLTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/sql/hand/custom/sybase/SybaseCustomSQLTest.java @@ -12,6 +12,7 @@ import org.hibernate.dialect.SybaseDialect; import org.hibernate.test.sql.hand.custom.CustomStoredProcTestSupport; import org.hibernate.testing.RequiresDialect; +import org.junit.Ignore; /** * Custom SQL tests for Sybase dialects @@ -19,6 +20,7 @@ * @author Gavin King */ @RequiresDialect( { SybaseDialect.class, SybaseASE15Dialect.class, Sybase11Dialect.class, SybaseAnywhereDialect.class }) +@Ignore("The jTDS driver doesn't detect callable prepared statements and can't unwrap from a PreparedStatement to a CallableStatement") public class SybaseCustomSQLTest extends CustomStoredProcTestSupport { public String[] getMappings() { return new String[] { "sql/hand/custom/sybase/Mappings.hbm.xml" }; diff --git a/hibernate-core/src/test/java/org/hibernate/test/sql/hand/query/NativeSQLQueries.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/sql/hand/query/NativeSQLQueries.hbm.xml index 221ef07fe11b..4764a02a818e 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/sql/hand/query/NativeSQLQueries.hbm.xml +++ b/hibernate-core/src/test/java/org/hibernate/test/sql/hand/query/NativeSQLQueries.hbm.xml @@ -127,14 +127,14 @@ - + - + diff --git a/hibernate-core/src/test/java/org/hibernate/test/sql/hand/query/NativeSQLQueriesTest.java b/hibernate-core/src/test/java/org/hibernate/test/sql/hand/query/NativeSQLQueriesTest.java index abbc3a7fcf80..1cb6100f96bf 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/sql/hand/query/NativeSQLQueriesTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/sql/hand/query/NativeSQLQueriesTest.java @@ -10,6 +10,9 @@ import java.io.Serializable; import java.math.BigDecimal; import java.math.BigInteger; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.SQLException; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; @@ -42,6 +45,7 @@ import org.hibernate.testing.FailureExpected; import org.hibernate.testing.RequiresDialect; import org.hibernate.testing.SkipForDialect; +import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.hibernate.test.sql.hand.Dimension; import org.hibernate.test.sql.hand.Employment; @@ -102,7 +106,7 @@ protected String getEmploymentSQL() { } protected String getEmploymentSQLMixedScalarEntity() { - return "SELECT e.*, e.employer as employerid FROM EMPLOYMENT e" ; + return "SELECT e.*, e.EMPLOYER as employerid FROM EMPLOYMENT e" ; } protected String getOrgEmpRegionSQL() { @@ -845,8 +849,21 @@ public void testTextTypeInSQLQuery() { s = openSession(); t = s.beginTransaction(); - String descriptionRead = ( String ) s.createSQLQuery( getDescriptionsSQL() ) + Object result = s.createSQLQuery( getDescriptionsSQL() ) .uniqueResult(); + String descriptionRead; + if ( result instanceof String ) { + descriptionRead = (String) result; + } + else { + Clob clob = (Clob) result; + try { + descriptionRead = clob.getSubString( 1L, (int) clob.length() ); + } + catch (SQLException e) { + throw new RuntimeException( e ); + } + } assertEquals( description, descriptionRead ); s.delete( holder ); t.commit(); @@ -858,7 +875,8 @@ public void testTextTypeInSQLQuery() { public void testImageTypeInSQLQuery() { Session s = openSession(); Transaction t = s.beginTransaction(); - byte[] photo = buildLongByteArray( 15000, true ); + // Make sure the last byte is non-zero as Sybase cuts that off + byte[] photo = buildLongByteArray( 14999, true ); ImageHolder holder = new ImageHolder( photo ); s.persist( holder ); t.commit(); @@ -866,8 +884,21 @@ public void testImageTypeInSQLQuery() { s = openSession(); t = s.beginTransaction(); - byte[] photoRead = ( byte[] ) s.createSQLQuery( getPhotosSQL() ) + Object result = s.createSQLQuery( getPhotosSQL() ) .uniqueResult(); + byte[] photoRead; + if ( result instanceof byte[] ) { + photoRead = (byte[]) result; + } + else { + Blob blob = (Blob) result; + try { + photoRead = blob.getBytes( 1L, (int) blob.length() ); + } + catch (SQLException e) { + throw new RuntimeException( e ); + } + } assertTrue( Arrays.equals( photo, photoRead ) ); s.delete( holder ); t.commit(); @@ -885,6 +916,28 @@ public void testEscapeColonInSQL() throws QueryException { s.close(); } + @Test + @TestForIssue( jiraKey = "HHH-14487") + public void testAliasToBeanMap() { + Person gavin = new Person( "Gavin" ); + + Session s = openSession(); + Transaction t = s.beginTransaction(); + s.persist( gavin ); + t.commit(); + s.close(); + + s = openSession(); + t = s.beginTransaction(); + HashMap result = (HashMap) session.createNativeQuery( "select * from PERSON" ) + .setResultTransformer( Transformers.aliasToBean( HashMap.class ) ) + .uniqueResult(); + assertEquals( "Gavin", result.get( "NAME" ) == null ? result.get( "name" ) : result.get( "NAME" ) ); + session.delete( gavin ); + t.commit(); + s.close(); + } + private String buildLongString(int size, char baseChar) { StringBuilder buff = new StringBuilder(); for( int i = 0; i < size; i++ ) { diff --git a/hibernate-core/src/test/java/org/hibernate/test/timestamp/LocalDateCustomSessionLevelTimeZoneTest.java b/hibernate-core/src/test/java/org/hibernate/test/timestamp/LocalDateCustomSessionLevelTimeZoneTest.java deleted file mode 100644 index 050149e7e5e2..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/timestamp/LocalDateCustomSessionLevelTimeZoneTest.java +++ /dev/null @@ -1,111 +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 . - */ -package org.hibernate.test.timestamp; - -import java.time.LocalDate; -import java.util.Map; -import java.util.TimeZone; -import javax.persistence.Entity; -import javax.persistence.Id; - -import org.hibernate.cfg.AvailableSettings; -import org.hibernate.dialect.MySQL5Dialect; - -import org.hibernate.testing.RequiresDialect; -import org.hibernate.testing.TestForIssue; -import org.hibernate.testing.jdbc.ConnectionProviderDelegate; -import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; -import org.junit.Test; - -import static org.hibernate.testing.transaction.TransactionUtil.doInHibernateSessionBuilder; -import static org.junit.Assert.assertEquals; - -/** - * @author Vlad Mihalcea - */ -@RequiresDialect(MySQL5Dialect.class) -public class LocalDateCustomSessionLevelTimeZoneTest - extends BaseNonConfigCoreFunctionalTestCase { - - private static final TimeZone TIME_ZONE = TimeZone.getTimeZone( - "Europe/Berlin" ); - - private ConnectionProviderDelegate connectionProvider = new ConnectionProviderDelegate() { - @Override - public void configure(Map configurationValues) { - String url = (String) configurationValues.get( AvailableSettings.URL ); - if(!url.contains( "?" )) { - url += "?"; - } - else if(!url.endsWith( "&" )) { - url += "&"; - } - - url += "useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Europe/Berlin"; - - configurationValues.put( AvailableSettings.URL, url); - super.configure( configurationValues ); - } - }; - - @Override - protected Class[] getAnnotatedClasses() { - return new Class[] { - Person.class - }; - } - - @Override - protected void addSettings(Map settings) { - settings.put( - AvailableSettings.CONNECTION_PROVIDER, - connectionProvider - ); - } - - @Override - protected void releaseResources() { - super.releaseResources(); - connectionProvider.stop(); - } - - @Test - @TestForIssue( jiraKey = "HHH-11396" ) - public void testTimeZone() { - TimeZone old = TimeZone.getDefault(); - try { - // The producer (MySQL) Berlin and returns 1980-01-01 - TimeZone jdbcTimeZone = TimeZone.getTimeZone( "Europe/Berlin" ); - TimeZone.setDefault( jdbcTimeZone ); - - //hibernate.connection.url jdbc:mysql://localhost/hibernate_orm_test - doInHibernateSessionBuilder( () -> sessionFactory().withOptions().jdbcTimeZone( TIME_ZONE ), s -> { - Person person = new Person(); - person.id = 1L; - s.persist( person ); - } ); - - doInHibernateSessionBuilder( () -> sessionFactory().withOptions().jdbcTimeZone( TIME_ZONE ), s -> { - Person person = s.find( Person.class, 1L ); - assertEquals( LocalDate.of( 2017, 3, 7 ), person.createdOn ); - } ); - } - finally { - TimeZone.setDefault( old ); - } - } - - @Entity(name = "Person") - public static class Person { - - @Id - private Long id; - - private LocalDate createdOn = LocalDate.of( 2017, 3, 7 ); - } -} - diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/InstantTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/InstantTest.java index 1b91e48cc336..56412b9ce320 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/InstantTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/InstantTest.java @@ -25,6 +25,7 @@ import org.hibernate.dialect.H2Dialect; import org.hibernate.dialect.MariaDBDialect; import org.hibernate.dialect.MySQLDialect; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.dialect.SybaseDialect; import org.junit.runners.Parameterized; @@ -89,8 +90,12 @@ public static List data() { ) // => Also test DST start, just in case .add( 2018, 3, 25, 1, 0, 0, 0, ZONE_PARIS ) - .add( 2018, 3, 25, 2, 0, 0, 0, ZONE_PARIS ) - .add( 2018, 9, 30, 2, 0, 0, 0, ZONE_AUCKLAND ) + .skippedForDialects( + // No idea what Sybase is doing here exactly + dialect -> dialect instanceof SybaseASE15Dialect, + b -> b.add( 2018, 3, 25, 2, 0, 0, 0, ZONE_PARIS ) + .add( 2018, 9, 30, 2, 0, 0, 0, ZONE_AUCKLAND ) + ) .add( 2018, 9, 30, 3, 0, 0, 0, ZONE_AUCKLAND ) // => Also test dates around 1905-01-01, because the code behaves differently before and after 1905 .add( 1904, 12, 31, 22, 59, 59, 999_999_999, ZONE_PARIS ) diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/LobUnfetchedPropertyTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/LobUnfetchedPropertyTest.java index 092e268d471b..13841c2e6778 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/LobUnfetchedPropertyTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/LobUnfetchedPropertyTest.java @@ -29,8 +29,11 @@ import org.hibernate.Hibernate; import org.hibernate.annotations.Cache; import org.hibernate.annotations.CacheConcurrencyStrategy; +import org.hibernate.dialect.SybaseASE15Dialect; + import org.hibernate.testing.DialectChecks; import org.hibernate.testing.RequiresDialectFeature; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; @@ -40,6 +43,7 @@ @TestForIssue(jiraKey = "HHH-12555") @RequiresDialectFeature(DialectChecks.SupportsExpectedLobUsagePattern.class) @RunWith(BytecodeEnhancerRunner.class) +@SkipForDialect( value = SybaseASE15Dialect.class, comment = "jTDS driver doesn't implement binary stream handling") public class LobUnfetchedPropertyTest extends BaseCoreFunctionalTestCase { @Override diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTest.java index 19dbb894577f..3e33ac46fac1 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTest.java @@ -26,6 +26,7 @@ import org.hibernate.dialect.MariaDBDialect; import org.hibernate.dialect.MySQL5Dialect; import org.hibernate.dialect.MySQLDialect; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.type.descriptor.sql.TimestampTypeDescriptor; import org.hibernate.testing.SkipForDialect; @@ -72,7 +73,11 @@ public static List data() { .add( 1892, 1, 1, ZONE_OSLO ) .add( 1900, 1, 1, ZONE_PARIS ) .add( 1900, 1, 1, ZONE_AMSTERDAM ) - .add( 1600, 1, 1, ZONE_AMSTERDAM ) + ) + .skippedForDialects( + // No idea what Sybase is doing here exactly + dialect -> dialect instanceof SybaseASE15Dialect, + b -> b.add( 1600, 1, 1, ZONE_AMSTERDAM ) ) // HHH-13379: DST end (where Timestamp becomes ambiguous, see JDK-4312621) // It doesn't seem that any date at midnight can be affected by HHH-13379, but we add some tests just in case diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/TimeAndTimestampTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/TimeAndTimestampTest.java index 6497d3165108..ede7d8aa2b22 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/TimeAndTimestampTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/TimeAndTimestampTest.java @@ -48,14 +48,14 @@ public void test() { Event event = new Event(); event.id = 1L; event.timeValue = new Time( 1000 ); - event.timestampValue = new Timestamp( 45678 ); + event.timestampValue = new Timestamp( 45677 ); session.persist( event ); } ); doInHibernate( this::sessionFactory, session -> { Event event = session.find( Event.class, 1L ); assertEquals(1000, event.timeValue.getTime() % TimeUnit.DAYS.toMillis( 1 )); - assertEquals(45678, event.timestampValue.getTime() % TimeUnit.DAYS.toMillis( 1 )); + assertEquals(45677, event.timestampValue.getTime() % TimeUnit.DAYS.toMillis( 1 )); } ); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/typedescriptor/ByteTest.java b/hibernate-core/src/test/java/org/hibernate/test/typedescriptor/ByteTest.java index a7351de8dfd7..ca5aec6b7f0c 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/typedescriptor/ByteTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/typedescriptor/ByteTest.java @@ -11,6 +11,9 @@ import org.hibernate.Session; import org.hibernate.Transaction; +import org.hibernate.dialect.SybaseASE15Dialect; + +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; @@ -29,6 +32,7 @@ protected Class[] getAnnotatedClasses() { @Test @TestForIssue( jiraKey = "HHH-6533" ) + @SkipForDialect( value = SybaseASE15Dialect.class, comment = "Didn't check what's the problem, but I think the DDL type chosen for byte is unsigned") public void testByteDataPersistenceAndRetrieval() { Session session = openSession(); Transaction transaction = session.beginTransaction(); diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyManyToManyNonUniqueIdWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyManyToManyNonUniqueIdWhereTest.java index 574f2614edb7..00fb2c690689 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyManyToManyNonUniqueIdWhereTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyManyToManyNonUniqueIdWhereTest.java @@ -300,7 +300,7 @@ public void setSizesFromCombined(Set sizesFromCombined) { inverseJoinColumns = { @JoinColumn( name = "ASSOCIATION_ID" ) } ) @WhereJoinTable( clause = "MAIN_CODE='MATERIAL' AND ASSOCIATION_CODE='RATING'" ) - @Where( clause = "name = 'high' or name = 'medium'" ) + @Where( clause = "NAME = 'high' or NAME = 'medium'" ) @Immutable public List getMediumOrHighRatingsFromCombined() { return mediumOrHighRatingsFromCombined; @@ -387,7 +387,7 @@ public void setRatingsFromCombined(Set ratingsFromCombined) { joinColumns = { @JoinColumn( name = "BUILDING_ID") }, inverseJoinColumns = { @JoinColumn( name = "RATING_ID" ) } ) - @Where( clause = "name = 'high' or name = 'medium'" ) + @Where( clause = "NAME = 'high' or NAME = 'medium'" ) @Immutable public List getMediumOrHighRatings() { return mediumOrHighRatings; diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyOneToManyNonUniqueIdWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyOneToManyNonUniqueIdWhereTest.java index c30d9bf6db3f..c4a8d43e73c2 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyOneToManyNonUniqueIdWhereTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyOneToManyNonUniqueIdWhereTest.java @@ -197,7 +197,7 @@ public void setSizesFromCombined(Set sizesFromCombined) { @OneToMany @JoinColumn( name = "MATERIAL_OWNER_ID") - @Where( clause = "name = 'high' or name = 'medium'" ) + @Where( clause = "NAME = 'high' or NAME = 'medium'" ) @Immutable public List getMediumOrHighRatingsFromCombined() { return mediumOrHighRatingsFromCombined; diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyManyToManyNonUniqueIdWhereTest.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyManyToManyNonUniqueIdWhereTest.hbm.xml index d0b365853c51..8bba29ab56ce 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyManyToManyNonUniqueIdWhereTest.hbm.xml +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyManyToManyNonUniqueIdWhereTest.hbm.xml @@ -22,7 +22,7 @@ where="MAIN_CODE='MATERIAL' AND ASSOCIATION_CODE='RATING'"> + where="NAME = 'high' or NAME = 'medium'"/> @@ -54,7 +54,7 @@ + where="NAME = 'high' or NAME = 'medium'"/> diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyOneToManyNonUniqueIdWhereTest.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyOneToManyNonUniqueIdWhereTest.hbm.xml index caf46130696f..9925130fa286 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyOneToManyNonUniqueIdWhereTest.hbm.xml +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyOneToManyNonUniqueIdWhereTest.hbm.xml @@ -18,7 +18,7 @@ + where="NAME = 'high' or NAME = 'medium'"> diff --git a/hibernate-core/src/test/resources/hibernate.properties b/hibernate-core/src/test/resources/hibernate.properties index de12583ef4c8..cb2fcfccb984 100644 --- a/hibernate-core/src/test/resources/hibernate.properties +++ b/hibernate-core/src/test/resources/hibernate.properties @@ -10,6 +10,7 @@ hibernate.connection.driver_class @jdbc.driver@ hibernate.connection.url @jdbc.url@ hibernate.connection.username @jdbc.user@ hibernate.connection.password @jdbc.pass@ +hibernate.connection.init_sql @connection.init_sql@ hibernate.connection.pool_size 5 diff --git a/hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualifiedTableNaming/database-object-using-catalog-placeholder.hbm.xml b/hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualifiedTableNaming/database-object-using-catalog-placeholder.hbm.xml new file mode 100644 index 000000000000..124c2ebcaea3 --- /dev/null +++ b/hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualifiedTableNaming/database-object-using-catalog-placeholder.hbm.xml @@ -0,0 +1,30 @@ + + + + + + + CREATE OR REPLACE FUNCTION ${catalog}.catalogPrefixedAuxObject() + RETURNS varchar AS + $BODY$ + BEGIN + SELECT 'test'; + END; + $BODY$ + LANGUAGE plpgsql + + DROP FUNCTION ${catalog}.catalogPrefixedAuxObject() + + diff --git a/hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualifiedTableNaming/database-object-using-schema-placeholder.hbm.xml b/hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualifiedTableNaming/database-object-using-schema-placeholder.hbm.xml new file mode 100644 index 000000000000..55375864981b --- /dev/null +++ b/hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualifiedTableNaming/database-object-using-schema-placeholder.hbm.xml @@ -0,0 +1,30 @@ + + + + + + + CREATE OR REPLACE FUNCTION ${schema}.schemaPrefixedAuxObject() + RETURNS varchar AS + $BODY$ + BEGIN + SELECT 'test'; + END; + $BODY$ + LANGUAGE plpgsql + + DROP FUNCTION ${schema}.schemaPrefixedAuxObject() + + diff --git a/hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualfiedTableNaming/implicit-file-level-catalog-and-schema.hbm.xml b/hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualifiedTableNaming/implicit-file-level-catalog-and-schema.hbm.xml similarity index 68% rename from hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualfiedTableNaming/implicit-file-level-catalog-and-schema.hbm.xml rename to hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualifiedTableNaming/implicit-file-level-catalog-and-schema.hbm.xml index fc5317d20da2..c89042cdb466 100644 --- a/hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualfiedTableNaming/implicit-file-level-catalog-and-schema.hbm.xml +++ b/hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualifiedTableNaming/implicit-file-level-catalog-and-schema.hbm.xml @@ -8,10 +8,19 @@ - - + Some entity-level comment + + + Some column-level comment for "id" + + + + + Some column-level comment for "property" + + diff --git a/hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualfiedTableNaming/implicit-file-level-catalog-and-schema.orm.xml b/hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualifiedTableNaming/implicit-file-level-catalog-and-schema.orm.xml similarity index 96% rename from hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualfiedTableNaming/implicit-file-level-catalog-and-schema.orm.xml rename to hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualifiedTableNaming/implicit-file-level-catalog-and-schema.orm.xml index cff6d97f8bc7..7d9458db2cee 100644 --- a/hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualfiedTableNaming/implicit-file-level-catalog-and-schema.orm.xml +++ b/hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualifiedTableNaming/implicit-file-level-catalog-and-schema.orm.xml @@ -10,7 +10,7 @@ xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence/orm http://xmlns.jcp.org/xml/ns/persistence/orm_2_1.xsd" version="2.1"> - org.hibernate.test.boot.database.qualfiedTableNaming + org.hibernate.test.boot.database.qualifiedTableNaming someImplicitFileLevelSchema someImplicitFileLevelCatalog + + + + Some entity-level comment + + + Some column-level comment for "id" + + + + + Some column-level comment for "property" + + + + diff --git a/hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualifiedTableNaming/no-file-level-catalog-and-schema.orm.xml b/hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualifiedTableNaming/no-file-level-catalog-and-schema.orm.xml new file mode 100644 index 000000000000..084f1dfd460c --- /dev/null +++ b/hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualifiedTableNaming/no-file-level-catalog-and-schema.orm.xml @@ -0,0 +1,44 @@ + + + + org.hibernate.test.boot.database.qualifiedTableNaming + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hibernate-ehcache/src/test/java/org/hibernate/cache/ehcache/test/domain/HolidayCalendar.java b/hibernate-ehcache/src/test/java/org/hibernate/cache/ehcache/test/domain/HolidayCalendar.java index 7d5ecea3e641..5a9749400615 100644 --- a/hibernate-ehcache/src/test/java/org/hibernate/cache/ehcache/test/domain/HolidayCalendar.java +++ b/hibernate-ehcache/src/test/java/org/hibernate/cache/ehcache/test/domain/HolidayCalendar.java @@ -19,10 +19,12 @@ public class HolidayCalendar { private Long id; + private String name; // Date -> String private Map holidays = new HashMap(); public HolidayCalendar init() { + name = "default"; DateFormat df = new SimpleDateFormat("yyyy.MM.dd"); try { holidays.clear(); @@ -35,6 +37,14 @@ public HolidayCalendar init() { return this; } + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + public Map getHolidays() { return holidays; } diff --git a/hibernate-ehcache/src/test/resources/hibernate-config/domain/HolidayCalendar.hbm.xml b/hibernate-ehcache/src/test/resources/hibernate-config/domain/HolidayCalendar.hbm.xml index 9ba308b2196a..686621ddf2b5 100644 --- a/hibernate-ehcache/src/test/resources/hibernate-config/domain/HolidayCalendar.hbm.xml +++ b/hibernate-ehcache/src/test/resources/hibernate-config/domain/HolidayCalendar.hbm.xml @@ -16,6 +16,8 @@ + + diff --git a/hibernate-envers-jakarta/hibernate-envers-jakarta.gradle b/hibernate-envers-jakarta/hibernate-envers-jakarta.gradle index 0a72f9ef9d08..4841a1e8bb35 100644 --- a/hibernate-envers-jakarta/hibernate-envers-jakarta.gradle +++ b/hibernate-envers-jakarta/hibernate-envers-jakarta.gradle @@ -1,4 +1,5 @@ import org.apache.tools.ant.filters.ReplaceTokens +import javax.inject.Inject /* * Hibernate, Relational Persistence for Idiomatic Java @@ -12,13 +13,22 @@ description = 'Hibernate\'s entity version (audit/history) support Jakarta editi apply from: rootProject.file( 'gradle/published-java-module.gradle' ) configurations { - jakartaeeTransformJars + jakartaeeTransformTool } +// we do not want the much of the normal java plugin's behavior +compileJava.enabled false +processResources.enabled false +compileTestJava.enabled false +processTestResources.enabled false +jar.enabled false +javadocJar.enabled false +sourcesJar.enabled false + dependencies { compile( project( ':hibernate-core-jakarta' ) ) - jakartaeeTransformJars 'biz.aQute.bnd:biz.aQute.bnd.transform:5.1.1', + jakartaeeTransformTool 'biz.aQute.bnd:biz.aQute.bnd.transform:5.1.1', 'commons-cli:commons-cli:1.4', 'org.slf4j:slf4j-simple:1.7.30', 'org.slf4j:slf4j-api:1.7.26', @@ -64,7 +74,7 @@ jar { } javaexec { - classpath configurations.jakartaeeTransformJars + classpath configurations.jakartaeeTransformTool main = 'org.eclipse.transformer.jakarta.JakartaTransformer' args = transformerArgs } @@ -72,6 +82,53 @@ jar { } } +task transformJar(type: JakartaJarTransformation) { + description 'Transforms the hibernate-envers jar using the JakartaTransformer tool' + + dependsOn project(':hibernate-envers').tasks.jar + mustRunAfter project(':hibernate-envers').tasks.jar + + sourceJar project(':hibernate-envers').tasks.jar.archiveFile + targetJar tasks.jar.archiveFile.get().asFile +} + +task transformSourcesJar(type: JakartaJarTransformation) { + description 'Transforms the hibernate-envers sources jar using the JakartaTransformer tool' + + dependsOn project(':hibernate-envers').tasks.sourcesJar + mustRunAfter project(':hibernate-envers').tasks.sourcesJar + + sourceJar project(':hibernate-envers').tasks.sourcesJar.archiveFile + targetJar tasks.sourcesJar.archiveFile.get().asFile +} + +task transformJavadocJar(type: JakartaJarTransformation) { + description 'Transforms the hibernate-envers javadoc jar using the JakartaTransformer tool' + + dependsOn project(':hibernate-envers').tasks.javadocJar + mustRunAfter project(':hibernate-envers').tasks.javadocJar + + sourceJar project(':hibernate-envers').tasks.javadocJar.archiveFile + targetJar tasks.javadocJar.archiveFile.get().asFile +} + +configurations { + [apiElements, runtimeElements].each { + it.outgoing.artifacts.removeIf { + it.buildDependencies.getDependencies(null).contains(jar) + } + it.outgoing.artifact(tasks.transformJar.targetJar) { + builtBy tasks.transformJar + } + it.outgoing.artifact(tasks.transformSourcesJar.targetJar) { + builtBy tasks.transformSourcesJar + } + it.outgoing.artifact(tasks.transformJavadocJar.targetJar) { + builtBy tasks.transformJavadocJar + } + } +} + task unpackTestJar(type: Copy) { dependsOn jar fileTree(project.buildDir).matching { include 'libs/*-test.jar' }.each { @@ -98,3 +155,53 @@ test { jvmArgs( ['--add-opens', 'java.base/java.lang=ALL-UNNAMED'] ) } } + +@CacheableTask +abstract class JakartaJarTransformation extends DefaultTask { + private final RegularFileProperty sourceJar; + private final RegularFileProperty targetJar; + + @Inject + JakartaJarTransformation(ObjectFactory objectFactory) { + sourceJar = objectFactory.fileProperty(); + targetJar = objectFactory.fileProperty(); + } + + @InputFile + @PathSensitive( PathSensitivity.RELATIVE ) + RegularFileProperty getSourceJar() { + return sourceJar; + } + + void sourceJar(Object fileReference) { + sourceJar.set( project.file( fileReference ) ) + } + + @OutputFile + RegularFileProperty getTargetJar() { + return targetJar; + } + + void targetJar(Object fileReference) { + targetJar.set( project.file( fileReference ) ) + } + + @TaskAction + void transform() { + project.javaexec( new Action() { + @Override + void execute(JavaExecSpec javaExecSpec) { + javaExecSpec.classpath( getProject().getConfigurations().getByName( "jakartaeeTransformTool" ) ); + javaExecSpec.setMain( "org.eclipse.transformer.jakarta.JakartaTransformer" ); + javaExecSpec.args( + sourceJar.get().getAsFile().getAbsolutePath(), + targetJar.get().getAsFile().getAbsolutePath(), + "-q", + "-tr", getProject().getRootProject().file( "rules/jakarta-renames.properties" ).getAbsolutePath(), + "-tv", getProject().getRootProject().file( "rules/jakarta-versions.properties" ).getAbsolutePath(), + "-td", getProject().getRootProject().file( "rules/jakarta-direct.properties" ).getAbsolutePath() + ); + } + }); + } +} \ No newline at end of file diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/internal/entities/mapper/relation/lazy/proxy/CollectionProxy.java b/hibernate-envers/src/main/java/org/hibernate/envers/internal/entities/mapper/relation/lazy/proxy/CollectionProxy.java index 3e96d93f797c..fe4d04532c52 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/internal/entities/mapper/relation/lazy/proxy/CollectionProxy.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/internal/entities/mapper/relation/lazy/proxy/CollectionProxy.java @@ -9,13 +9,15 @@ import java.io.Serializable; import java.util.Collection; import java.util.Iterator; +import org.hibernate.collection.spi.LazyInitializable; import org.hibernate.envers.internal.entities.mapper.relation.lazy.initializor.Initializor; /** * @author Adam Warski (adam at warski dot org) */ -public abstract class CollectionProxy> implements Collection, Serializable { +public abstract class CollectionProxy> implements Collection, LazyInitializable, Serializable { + private static final long serialVersionUID = 8698249863871832402L; private transient org.hibernate.envers.internal.entities.mapper.relation.lazy.initializor.Initializor initializor; @@ -34,6 +36,16 @@ protected void checkInit() { } } + @Override + public final boolean wasInitialized() { + return delegate != null; + } + + @Override + public final void forceInitialization() { + checkInit(); + } + @Override public int size() { checkInit(); @@ -118,7 +130,7 @@ public String toString() { return delegate.toString(); } - @SuppressWarnings({"EqualsWhichDoesntCheckParameterClass"}) + @SuppressWarnings({ "EqualsWhichDoesntCheckParameterClass" }) @Override public boolean equals(Object obj) { checkInit(); diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/internal/entities/mapper/relation/lazy/proxy/MapProxy.java b/hibernate-envers/src/main/java/org/hibernate/envers/internal/entities/mapper/relation/lazy/proxy/MapProxy.java index 6ee143ea00f2..1aeb142055be 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/internal/entities/mapper/relation/lazy/proxy/MapProxy.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/internal/entities/mapper/relation/lazy/proxy/MapProxy.java @@ -10,13 +10,14 @@ import java.util.Collection; import java.util.Map; import java.util.Set; - +import org.hibernate.collection.spi.LazyInitializable; import org.hibernate.envers.internal.entities.mapper.relation.lazy.initializor.Initializor; /** * @author Adam Warski (adam at warski dot org) */ -public class MapProxy implements Map, Serializable { +public class MapProxy implements Map, LazyInitializable, Serializable { + private static final long serialVersionUID = 8418037541773074646L; private transient Initializor> initializor; @@ -35,6 +36,16 @@ private void checkInit() { } } + @Override + public final boolean wasInitialized() { + return delegate != null; + } + + @Override + public final void forceInitialization() { + checkInit(); + } + @Override public int size() { checkInit(); @@ -114,7 +125,7 @@ public String toString() { } @Override - @SuppressWarnings({"EqualsWhichDoesntCheckParameterClass"}) + @SuppressWarnings({ "EqualsWhichDoesntCheckParameterClass" }) public boolean equals(Object obj) { checkInit(); return delegate.equals( obj ); diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/internal/entities/mapper/relation/lazy/proxy/SortedMapProxy.java b/hibernate-envers/src/main/java/org/hibernate/envers/internal/entities/mapper/relation/lazy/proxy/SortedMapProxy.java index 93658e133343..bad56e331511 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/internal/entities/mapper/relation/lazy/proxy/SortedMapProxy.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/internal/entities/mapper/relation/lazy/proxy/SortedMapProxy.java @@ -12,13 +12,14 @@ import java.util.Map; import java.util.Set; import java.util.SortedMap; - +import org.hibernate.collection.spi.LazyInitializable; import org.hibernate.envers.internal.entities.mapper.relation.lazy.initializor.Initializor; /** * @author Adam Warski (adam at warski dot org) */ -public class SortedMapProxy implements SortedMap, Serializable { +public class SortedMapProxy implements SortedMap, LazyInitializable, Serializable { + private static final long serialVersionUID = 2645817952901452375L; private transient Initializor> initializor; @@ -37,6 +38,16 @@ private void checkInit() { } } + @Override + public final boolean wasInitialized() { + return delegate != null; + } + + @Override + public final void forceInitialization() { + checkInit(); + } + @Override public int size() { checkInit(); @@ -146,7 +157,7 @@ public K lastKey() { } @Override - @SuppressWarnings({"EqualsWhichDoesntCheckParameterClass"}) + @SuppressWarnings({ "EqualsWhichDoesntCheckParameterClass" }) public boolean equals(Object o) { checkInit(); return delegate.equals( o ); diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/AbstractOneSessionTest.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/AbstractOneSessionTest.java index c3783600de42..f8c13215ebd4 100644 --- a/hibernate-envers/src/test/java/org/hibernate/envers/test/AbstractOneSessionTest.java +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/AbstractOneSessionTest.java @@ -50,6 +50,8 @@ public void init() throws URISyntaxException { } config.setProperty( Environment.USE_NEW_ID_GENERATOR_MAPPINGS, "true" ); config.setProperty( EnversSettings.USE_REVISION_ENTITY_WITH_NATIVE_ID, "false" ); + // These tests always use H2, so we reset the init_sql config here + config.setProperty( "hibernate.connection.init_sql", "" ); addProperties( config ); this.initMappings(); diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/collection/MultipleCollectionEntity.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/collection/MultipleCollectionEntity.java index 28124393377a..3222861dfc43 100644 --- a/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/collection/MultipleCollectionEntity.java +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/collection/MultipleCollectionEntity.java @@ -7,8 +7,9 @@ package org.hibernate.envers.test.entities.collection; import java.util.ArrayList; -import java.util.Collections; import java.util.List; +import java.util.Objects; + import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; @@ -26,6 +27,7 @@ @Entity @Audited public class MultipleCollectionEntity { + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "ID", length = 10) @@ -65,7 +67,7 @@ public void setText(String text) { } public List getRefEntities1() { - return Collections.unmodifiableList( refEntities1 ); + return refEntities1; } public void addRefEntity1(MultipleCollectionRefEntity1 refEntity1) { @@ -77,7 +79,7 @@ public void removeRefEntity1(MultipleCollectionRefEntity1 refEntity1) { } public List getRefEntities2() { - return Collections.unmodifiableList( refEntities2 ); + return refEntities2; } public void addRefEntity2(MultipleCollectionRefEntity2 refEntity2) { @@ -110,34 +112,20 @@ public String toString() { } @Override - public boolean equals(Object o) { - if ( this == o ) { - return true; - } - if ( !(o instanceof MultipleCollectionEntity) ) { - return false; - } - - MultipleCollectionEntity that = (MultipleCollectionEntity) o; + public int hashCode() { + return Objects.hash( id ); + } - if ( refEntities1 != null ? !refEntities1.equals( that.refEntities1 ) : that.refEntities1 != null ) { - return false; - } - if ( refEntities2 != null ? !refEntities2.equals( that.refEntities2 ) : that.refEntities2 != null ) { + @Override + public boolean equals(Object obj) { + if ( this == obj ) + return true; + if ( obj == null ) return false; - } - if ( text != null ? !text.equals( that.text ) : that.text != null ) { + if ( getClass() != obj.getClass() ) return false; - } - - return true; + MultipleCollectionEntity other = (MultipleCollectionEntity) obj; + return Objects.equals( id, other.id ); } - @Override - public int hashCode() { - int result = text != null ? text.hashCode() : 0; - result = 31 * result + (refEntities1 != null ? refEntities1.hashCode() : 0); - result = 31 * result + (refEntities2 != null ? refEntities2.hashCode() : 0); - return result; - } } \ No newline at end of file diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/StringMapLobTest.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/StringMapLobTest.java index 347b9dfc9289..2e4f2f5c8c70 100644 --- a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/StringMapLobTest.java +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/StringMapLobTest.java @@ -20,6 +20,7 @@ import org.hibernate.dialect.DerbyDialect; import org.hibernate.dialect.Oracle8iDialect; import org.hibernate.dialect.PostgreSQL81Dialect; +import org.hibernate.dialect.SybaseDialect; import org.hibernate.envers.Audited; import org.hibernate.envers.test.BaseEnversJPAFunctionalTestCase; import org.hibernate.envers.test.Priority; @@ -39,6 +40,7 @@ @SkipForDialect(Oracle8iDialect.class) @SkipForDialect(value = PostgreSQL81Dialect.class, jiraKey = "HHH-11477", comment = "@Lob field in HQL predicate fails with error about text = bigint") @SkipForDialect(value = AbstractHANADialect.class, comment = "HANA doesn't support comparing LOBs with the = operator") +@SkipForDialect(value = SybaseDialect.class, comment = "Sybase doesn't support comparing LOBs with the = operator") @SkipForDialect(value = DB2Dialect.class, comment = "DB2 jdbc driver doesn't support setNString") @SkipForDialect(value = DerbyDialect.class, comment = "Derby jdbc driver doesn't support setNString") public class StringMapLobTest extends BaseEnversJPAFunctionalTestCase { diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/customtype/UnspecifiedEnumTypeTest.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/customtype/UnspecifiedEnumTypeTest.java index f2483ecfb4eb..86d01512827c 100644 --- a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/customtype/UnspecifiedEnumTypeTest.java +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/customtype/UnspecifiedEnumTypeTest.java @@ -104,7 +104,7 @@ public void testEnumRepresentation() { @SuppressWarnings("unchecked") List values = session - .createNativeQuery( "SELECT enum1 e1, enum2 e2 FROM ENUM_ENTITY_AUD ORDER BY rev ASC" ) + .createNativeQuery( "SELECT enum1 e1, enum2 e2 FROM ENUM_ENTITY_AUD ORDER BY REV ASC" ) .addScalar( "e1", IntegerType.INSTANCE ) .addScalar( "e2", IntegerType.INSTANCE ) .list(); diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/data/Lobs.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/data/Lobs.java index afddaecdcb1f..76c41b96a46f 100644 --- a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/data/Lobs.java +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/data/Lobs.java @@ -12,10 +12,12 @@ import javax.persistence.EntityManager; import org.hibernate.dialect.PostgreSQL82Dialect; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.envers.test.BaseEnversJPAFunctionalTestCase; import org.hibernate.envers.test.Priority; import org.hibernate.testing.DialectChecks; import org.hibernate.testing.RequiresDialectFeature; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import org.junit.Test; @@ -26,6 +28,7 @@ * @author Chris Cranford */ @RequiresDialectFeature(DialectChecks.SupportsExpectedLobUsagePattern.class) +@SkipForDialect( value = SybaseASE15Dialect.class, comment = "jTDS driver doesn't implement binary stream handling") public class Lobs extends BaseEnversJPAFunctionalTestCase { private Integer id1; private Integer id2; diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/multiplerelations/GroupMemberTest.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/multiplerelations/GroupMemberTest.java index 70d431b87d2d..01bc64839afc 100644 --- a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/multiplerelations/GroupMemberTest.java +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/multiplerelations/GroupMemberTest.java @@ -89,7 +89,7 @@ private Integer getCurrentAuditUniqueGroupId() { return TransactionUtil.doInJPA( this::entityManagerFactory, entityManager -> { final Session session = entityManager.unwrap( Session.class ); final Query query = session.createSQLQuery( - "SELECT uniqueGroup_id FROM GroupMember_AUD ORDER BY rev DESC" ).addScalar( + "SELECT uniqueGroup_id FROM GroupMember_AUD ORDER BY REV DESC" ).addScalar( "uniqueGroup_id", IntegerType.INSTANCE ).setMaxResults( 1 ); diff --git a/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/lazy/IsCollectionInitializedBytecodeEnhancementTest.java b/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/lazy/IsCollectionInitializedBytecodeEnhancementTest.java new file mode 100644 index 000000000000..fb609ff0b211 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/lazy/IsCollectionInitializedBytecodeEnhancementTest.java @@ -0,0 +1,98 @@ +/* + * 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.envers.integration.lazy; + +import java.util.List; +import javax.persistence.EntityManager; + +import org.hibernate.dialect.OracleDialect; +import org.hibernate.envers.query.AuditEntity; +import org.hibernate.envers.test.BaseEnversJPAFunctionalTestCase; +import org.hibernate.envers.test.Priority; +import org.hibernate.envers.test.entities.collection.MultipleCollectionEntity; +import org.hibernate.envers.test.entities.collection.MultipleCollectionRefEntity1; +import org.hibernate.envers.test.entities.collection.MultipleCollectionRefEntity2; +import org.hibernate.testing.SkipForDialect; +import org.hibernate.testing.TestForIssue; +import org.junit.Test; +import org.junit.runner.RunWith; +import static org.junit.Assert.assertEquals; +import org.hibernate.Hibernate; +import org.hibernate.envers.AuditReader; +import org.hibernate.envers.AuditReaderFactory; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.EnhancementOptions; + +/** + * @author Fabricio Gregorio + */ +@TestForIssue(jiraKey = "HHH-15522") +@RunWith(BytecodeEnhancerRunner.class) +@EnhancementOptions(lazyLoading = true) +@SkipForDialect(value = OracleDialect.class, comment = "Oracle does not support identity key generation") +public class IsCollectionInitializedBytecodeEnhancementTest extends BaseEnversJPAFunctionalTestCase { + + private Long mce1Id = null; + private Long mcre1Id = null; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[]{ + MultipleCollectionEntity.class, MultipleCollectionRefEntity1.class, MultipleCollectionRefEntity2.class + }; + } + + @Test + @Priority(10) + public void initData() { + EntityManager em = getEntityManager(); + + // Revision 1 - addition. + em.getTransaction().begin(); + MultipleCollectionEntity mce1 = new MultipleCollectionEntity(); + mce1.setText( "MultipleCollectionEntity-1-1" ); + em.persist( mce1 ); // Persisting entity with empty collections. + em.getTransaction().commit(); + + mce1Id = mce1.getId(); + + // Revision 2 - update. + em.getTransaction().begin(); + mce1 = em.find( MultipleCollectionEntity.class, mce1.getId() ); + MultipleCollectionRefEntity1 mcre1 = new MultipleCollectionRefEntity1(); + mcre1.setText( "MultipleCollectionRefEntity1-1-1" ); + mcre1.setMultipleCollectionEntity( mce1 ); + mce1.addRefEntity1( mcre1 ); + em.persist( mcre1 ); + mce1 = em.merge( mce1 ); + em.getTransaction().commit(); + + mcre1Id = mcre1.getId(); + + em.close(); + } + + @Test + @SuppressWarnings("unchecked") + public void testIsInitialized() { + EntityManager em = getEntityManager(); + + AuditReader reader = AuditReaderFactory.get( em ); + List res = reader.createQuery().forEntitiesAtRevision( MultipleCollectionEntity.class, 1 ) + .add( AuditEntity.id().eq( mce1Id ) ) + .getResultList(); + + MultipleCollectionEntity ret = res.get( 0 ); + + assertEquals( Hibernate.isInitialized( ret.getRefEntities1() ), false ); + + Hibernate.initialize(ret.getRefEntities1()); + + assertEquals( Hibernate.isInitialized( ret.getRefEntities1() ), true ); + + } +} diff --git a/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/lazy/IsCollectionInitializedTest.java b/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/lazy/IsCollectionInitializedTest.java new file mode 100644 index 000000000000..23e3933c81df --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/lazy/IsCollectionInitializedTest.java @@ -0,0 +1,93 @@ +/* + * 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.envers.integration.lazy; + +import java.util.List; +import javax.persistence.EntityManager; + +import org.hibernate.dialect.OracleDialect; +import org.hibernate.envers.query.AuditEntity; +import org.hibernate.envers.test.BaseEnversJPAFunctionalTestCase; +import org.hibernate.envers.test.Priority; +import org.hibernate.envers.test.entities.collection.MultipleCollectionEntity; +import org.hibernate.envers.test.entities.collection.MultipleCollectionRefEntity1; +import org.hibernate.envers.test.entities.collection.MultipleCollectionRefEntity2; +import org.hibernate.testing.SkipForDialect; +import org.hibernate.testing.TestForIssue; +import org.junit.Test; +import static org.junit.Assert.assertEquals; +import org.hibernate.Hibernate; +import org.hibernate.envers.AuditReader; +import org.hibernate.envers.AuditReaderFactory; + +/** + * @author Fabricio Gregorio + */ +@TestForIssue(jiraKey = "HHH-15522") +@SkipForDialect(value = OracleDialect.class, comment = "Oracle does not support identity key generation") +public class IsCollectionInitializedTest extends BaseEnversJPAFunctionalTestCase { + + private Long mce1Id = null; + private Long mcre1Id = null; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[]{ + MultipleCollectionEntity.class, MultipleCollectionRefEntity1.class, MultipleCollectionRefEntity2.class + }; + } + + @Test + @Priority(10) + public void initData() { + EntityManager em = getEntityManager(); + + // Revision 1 - addition. + em.getTransaction().begin(); + MultipleCollectionEntity mce1 = new MultipleCollectionEntity(); + mce1.setText( "MultipleCollectionEntity-1-1" ); + em.persist( mce1 ); // Persisting entity with empty collections. + em.getTransaction().commit(); + + mce1Id = mce1.getId(); + + // Revision 2 - update. + em.getTransaction().begin(); + mce1 = em.find( MultipleCollectionEntity.class, mce1.getId() ); + MultipleCollectionRefEntity1 mcre1 = new MultipleCollectionRefEntity1(); + mcre1.setText( "MultipleCollectionRefEntity1-1-1" ); + mcre1.setMultipleCollectionEntity( mce1 ); + mce1.addRefEntity1( mcre1 ); + em.persist( mcre1 ); + mce1 = em.merge( mce1 ); + em.getTransaction().commit(); + + mcre1Id = mcre1.getId(); + + em.close(); + } + + @Test + @SuppressWarnings("unchecked") + public void testIsInitialized() { + EntityManager em = getEntityManager(); + + AuditReader reader = AuditReaderFactory.get( em ); + List res = reader.createQuery().forEntitiesAtRevision( MultipleCollectionEntity.class, 1 ) + .add( AuditEntity.id().eq( mce1Id ) ) + .getResultList(); + + MultipleCollectionEntity ret = res.get( 0 ); + + assertEquals( Hibernate.isInitialized( ret.getRefEntities1() ), false ); + + Hibernate.initialize(ret.getRefEntities1()); + + assertEquals( Hibernate.isInitialized( ret.getRefEntities1() ), true ); + + } +} diff --git a/hibernate-envers/src/test/resources/hibernate.properties b/hibernate-envers/src/test/resources/hibernate.properties index f983fab1f46f..17fa883cd061 100644 --- a/hibernate-envers/src/test/resources/hibernate.properties +++ b/hibernate-envers/src/test/resources/hibernate.properties @@ -9,6 +9,7 @@ hibernate.connection.driver_class @jdbc.driver@ hibernate.connection.url @jdbc.url@ hibernate.connection.username @jdbc.user@ hibernate.connection.password @jdbc.pass@ +hibernate.connection.init_sql @connection.init_sql@ hibernate.connection.pool_size 5 diff --git a/hibernate-graalvm/src/main/java/org/hibernate/graalvm/internal/GraalVMStaticAutofeature.java b/hibernate-graalvm/src/main/java/org/hibernate/graalvm/internal/GraalVMStaticFeature.java similarity index 78% rename from hibernate-graalvm/src/main/java/org/hibernate/graalvm/internal/GraalVMStaticAutofeature.java rename to hibernate-graalvm/src/main/java/org/hibernate/graalvm/internal/GraalVMStaticFeature.java index 34fb70f6ed17..401c0853076e 100644 --- a/hibernate-graalvm/src/main/java/org/hibernate/graalvm/internal/GraalVMStaticAutofeature.java +++ b/hibernate-graalvm/src/main/java/org/hibernate/graalvm/internal/GraalVMStaticFeature.java @@ -12,7 +12,6 @@ import org.hibernate.internal.util.ReflectHelper; -import com.oracle.svm.core.annotate.AutomaticFeature; import org.graalvm.nativeimage.hosted.Feature; import org.graalvm.nativeimage.hosted.RuntimeReflection; @@ -34,26 +33,29 @@ *

      * @author Sanne Grinovero */ -@AutomaticFeature -public class GraalVMStaticAutofeature implements Feature { +public class GraalVMStaticFeature implements Feature { public void beforeAnalysis(Feature.BeforeAnalysisAccess before) { final Class[] needsHavingSimpleConstructors = StaticClassLists.typesNeedingDefaultConstructorAccessible(); - final Class[] neddingAllConstructorsAccessible = StaticClassLists.typesNeedingAllConstructorsAccessible(); + final Class[] needingAllConstructorsAccessible = StaticClassLists.typesNeedingAllConstructorsAccessible(); //Size formula is just a reasonable guess: - ArrayList executables = new ArrayList<>( needsHavingSimpleConstructors.length + neddingAllConstructorsAccessible.length * 3 ); - for ( Class c : needsHavingSimpleConstructors ) { + ArrayList executables = new ArrayList<>( needsHavingSimpleConstructors.length + needingAllConstructorsAccessible.length * 3 ); + for ( Class c : needsHavingSimpleConstructors ) { executables.add( ReflectHelper.getDefaultConstructor( c ) ); } - for ( Class c : neddingAllConstructorsAccessible ) { - for ( Constructor declaredConstructor : c.getDeclaredConstructors() ) { + for ( Class c : needingAllConstructorsAccessible) { + for ( Constructor declaredConstructor : c.getDeclaredConstructors() ) { executables.add( declaredConstructor ); } } RuntimeReflection.register( needsHavingSimpleConstructors ); - RuntimeReflection.register( neddingAllConstructorsAccessible ); + RuntimeReflection.register( needingAllConstructorsAccessible ); RuntimeReflection.register( StaticClassLists.typesNeedingArrayCopy() ); RuntimeReflection.register( executables.toArray(new Executable[0]) ); } + //@Override Method overridden in later API versions of GraalVM + public String getDescription() { + return "Hibernate ORM's static reflection registrations for GraalVM"; + } } diff --git a/hibernate-graalvm/src/main/java/org/hibernate/graalvm/internal/QueryParsingSupport.java b/hibernate-graalvm/src/main/java/org/hibernate/graalvm/internal/QueryParsingSupport.java index 28b1833e77d0..5e920da257f1 100644 --- a/hibernate-graalvm/src/main/java/org/hibernate/graalvm/internal/QueryParsingSupport.java +++ b/hibernate-graalvm/src/main/java/org/hibernate/graalvm/internal/QueryParsingSupport.java @@ -14,14 +14,13 @@ import org.hibernate.internal.build.AllowSysOut; import org.hibernate.internal.util.ReflectHelper; -import com.oracle.svm.core.annotate.AutomaticFeature; import org.graalvm.nativeimage.hosted.Feature; import org.graalvm.nativeimage.hosted.RuntimeReflection; /** * This registers all ANTLR parser nodes for reflection, something that is necessary * as the HQL parser's inner workings are based on reflection. - * This is different than the "static" registrations of {@link GraalVMStaticAutofeature} + * This is different than the "static" registrations of {@link GraalVMStaticFeature} * as we only register these if the HQL parser is actually reachable: some particularly * simple applications might not need dynamic queries being expressed in string form, * and for such cases the reflective registrations can be skipped. @@ -33,7 +32,6 @@ * * @author Sanne Grinovero */ -@AutomaticFeature public final class QueryParsingSupport implements Feature { private final AtomicBoolean triggered = new AtomicBoolean( false); @@ -51,6 +49,11 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { access.registerReachabilityHandler(this::enableHQLSupport, parserClazz); } + //@Override Method overridden in later API versions of GraalVM + public String getDescription() { + return "Hibernate ORM's support for HQL Parser in GraalVM"; + } + @AllowSysOut private void enableHQLSupport(DuringAnalysisAccess duringAnalysisAccess) { final boolean needsEnablingYet = triggered.compareAndSet( false, true ); diff --git a/hibernate-hikaricp/src/test/java/org/hibernate/test/hikaricp/HikariCPConnectionProviderTest.java b/hibernate-hikaricp/src/test/java/org/hibernate/test/hikaricp/HikariCPConnectionProviderTest.java index de38961c2062..37e52d32f2e7 100644 --- a/hibernate-hikaricp/src/test/java/org/hibernate/test/hikaricp/HikariCPConnectionProviderTest.java +++ b/hibernate-hikaricp/src/test/java/org/hibernate/test/hikaricp/HikariCPConnectionProviderTest.java @@ -11,10 +11,12 @@ import java.util.ArrayList; import java.util.List; +import org.hibernate.dialect.SybaseDialect; import org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.ConnectionProviderJdbcConnectionAccess; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.hikaricp.internal.HikariCPConnectionProvider; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.Test; @@ -27,6 +29,7 @@ /** * @author Brett Meyer */ +@SkipForDialect(value = SybaseDialect.class, comment = "The jTDS driver doesn't implement Connection#isValid so this fails") public class HikariCPConnectionProviderTest extends BaseCoreFunctionalTestCase { @Test diff --git a/hibernate-hikaricp/src/test/java/org/hibernate/test/hikaricp/HikariCPSkipAutoCommitTest.java b/hibernate-hikaricp/src/test/java/org/hibernate/test/hikaricp/HikariCPSkipAutoCommitTest.java index 491ceb6088f8..0fb2f0c16417 100644 --- a/hibernate-hikaricp/src/test/java/org/hibernate/test/hikaricp/HikariCPSkipAutoCommitTest.java +++ b/hibernate-hikaricp/src/test/java/org/hibernate/test/hikaricp/HikariCPSkipAutoCommitTest.java @@ -14,9 +14,11 @@ import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.Configuration; +import org.hibernate.dialect.SybaseDialect; import org.hibernate.testing.DialectChecks; import org.hibernate.testing.RequiresDialectFeature; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.hibernate.test.util.PreparedStatementSpyConnectionProvider; import org.junit.Test; @@ -32,6 +34,7 @@ * @author Vlad Mihalcea */ @RequiresDialectFeature(DialectChecks.SupportsJdbcDriverProxying.class) +@SkipForDialect(value = SybaseDialect.class, comment = "The jTDS driver doesn't implement Connection#isValid so this fails") public class HikariCPSkipAutoCommitTest extends BaseCoreFunctionalTestCase { private PreparedStatementSpyConnectionProvider connectionProvider = diff --git a/hibernate-hikaricp/src/test/java/org/hibernate/test/hikaricp/HikariTransactionIsolationConfigTest.java b/hibernate-hikaricp/src/test/java/org/hibernate/test/hikaricp/HikariTransactionIsolationConfigTest.java index 6893f9f90730..61df95790f8f 100644 --- a/hibernate-hikaricp/src/test/java/org/hibernate/test/hikaricp/HikariTransactionIsolationConfigTest.java +++ b/hibernate-hikaricp/src/test/java/org/hibernate/test/hikaricp/HikariTransactionIsolationConfigTest.java @@ -6,14 +6,17 @@ */ package org.hibernate.test.hikaricp; +import org.hibernate.dialect.SybaseDialect; import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; import org.hibernate.hikaricp.internal.HikariCPConnectionProvider; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.common.connections.BaseTransactionIsolationConfigTest; /** * @author Steve Ebersole */ +@SkipForDialect(value = SybaseDialect.class, comment = "The jTDS driver doesn't implement Connection#isValid so this fails") public class HikariTransactionIsolationConfigTest extends BaseTransactionIsolationConfigTest { @Override protected ConnectionProvider getConnectionProviderUnderTest() { diff --git a/hibernate-hikaricp/src/test/resources/hibernate.properties b/hibernate-hikaricp/src/test/resources/hibernate.properties index 506b085d54b5..a27b5a46fb9e 100644 --- a/hibernate-hikaricp/src/test/resources/hibernate.properties +++ b/hibernate-hikaricp/src/test/resources/hibernate.properties @@ -9,6 +9,7 @@ hibernate.connection.driver_class @jdbc.driver@ hibernate.connection.url @jdbc.url@ hibernate.connection.username @jdbc.user@ hibernate.connection.password @jdbc.pass@ +hibernate.connection.init_sql @connection.init_sql@ hibernate.jdbc.batch_size 10 hibernate.connection.provider_class HikariCPConnectionProvider diff --git a/hibernate-jcache/src/test/java/org/hibernate/jcache/test/RefreshUpdatedDataTest.java b/hibernate-jcache/src/test/java/org/hibernate/jcache/test/RefreshUpdatedDataTest.java index e62902d51992..8940cfa3906e 100644 --- a/hibernate-jcache/src/test/java/org/hibernate/jcache/test/RefreshUpdatedDataTest.java +++ b/hibernate-jcache/src/test/java/org/hibernate/jcache/test/RefreshUpdatedDataTest.java @@ -24,6 +24,8 @@ import org.hibernate.dialect.DerbyDialect; import org.hibernate.dialect.Dialect; import org.hibernate.dialect.H2Dialect; +import org.hibernate.dialect.HSQLDialect; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.service.ServiceRegistry; import org.hibernate.testing.SkipForDialect; @@ -89,6 +91,8 @@ public void releaseResources() { @Test @SkipForDialect(value = CockroachDB192Dialect.class, comment = "does not support nested transactions") @SkipForDialect(value = DerbyDialect.class, comment = "Derby does not support nested transactions") + @SkipForDialect(SybaseASE15Dialect.class) + @SkipForDialect(HSQLDialect.class) public void testUpdateAndFlushThenRefresh() { final String BEFORE = "before"; diff --git a/hibernate-jcache/src/test/java/org/hibernate/jcache/test/domain/HolidayCalendar.java b/hibernate-jcache/src/test/java/org/hibernate/jcache/test/domain/HolidayCalendar.java index c0e19f33fe4b..dcc7cfb1a7c0 100644 --- a/hibernate-jcache/src/test/java/org/hibernate/jcache/test/domain/HolidayCalendar.java +++ b/hibernate-jcache/src/test/java/org/hibernate/jcache/test/domain/HolidayCalendar.java @@ -19,10 +19,12 @@ public class HolidayCalendar { private Long id; + private String name; // Date -> String private Map holidays = new HashMap(); public HolidayCalendar init() { + name = "default"; DateFormat df = new SimpleDateFormat("yyyy.MM.dd"); try { holidays.clear(); @@ -35,6 +37,14 @@ public HolidayCalendar init() { return this; } + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + public Map getHolidays() { return holidays; } diff --git a/hibernate-jcache/src/test/resources/hibernate-config/domain/HolidayCalendar.hbm.xml b/hibernate-jcache/src/test/resources/hibernate-config/domain/HolidayCalendar.hbm.xml index 47822c93222f..583c1317a9e2 100644 --- a/hibernate-jcache/src/test/resources/hibernate-config/domain/HolidayCalendar.hbm.xml +++ b/hibernate-jcache/src/test/resources/hibernate-config/domain/HolidayCalendar.hbm.xml @@ -16,6 +16,8 @@
      + + diff --git a/hibernate-jcache/src/test/resources/hibernate.properties b/hibernate-jcache/src/test/resources/hibernate.properties index a643fcdfae23..c6fe2b13e598 100644 --- a/hibernate-jcache/src/test/resources/hibernate.properties +++ b/hibernate-jcache/src/test/resources/hibernate.properties @@ -9,6 +9,7 @@ hibernate.connection.driver_class @jdbc.driver@ hibernate.connection.url @jdbc.url@ hibernate.connection.username @jdbc.user@ hibernate.connection.password @jdbc.pass@ +hibernate.connection.init_sql @connection.init_sql@ hibernate.connection.pool_size 5 diff --git a/hibernate-micrometer/src/test/resources/hibernate.properties b/hibernate-micrometer/src/test/resources/hibernate.properties index de12583ef4c8..cb2fcfccb984 100644 --- a/hibernate-micrometer/src/test/resources/hibernate.properties +++ b/hibernate-micrometer/src/test/resources/hibernate.properties @@ -10,6 +10,7 @@ hibernate.connection.driver_class @jdbc.driver@ hibernate.connection.url @jdbc.url@ hibernate.connection.username @jdbc.user@ hibernate.connection.password @jdbc.pass@ +hibernate.connection.init_sql @connection.init_sql@ hibernate.connection.pool_size 5 diff --git a/hibernate-proxool/src/test/resources/hibernate.properties b/hibernate-proxool/src/test/resources/hibernate.properties index 8343ae53a458..a216660df551 100644 --- a/hibernate-proxool/src/test/resources/hibernate.properties +++ b/hibernate-proxool/src/test/resources/hibernate.properties @@ -9,6 +9,7 @@ hibernate.connection.driver_class @jdbc.driver@ hibernate.connection.url @jdbc.url@ hibernate.connection.username @jdbc.user@ hibernate.connection.password @jdbc.pass@ +hibernate.connection.init_sql @connection.init_sql@ hibernate.connection.pool_size 5 hibernate.jdbc.batch_size 10 diff --git a/hibernate-spatial/databases/postgispg96/matrix.gradle b/hibernate-spatial/databases/postgispg96/matrix.gradle index f59a2fafc469..21b9703e577c 100644 --- a/hibernate-spatial/databases/postgispg96/matrix.gradle +++ b/hibernate-spatial/databases/postgispg96/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.postgresql:postgresql:42.2.16' \ No newline at end of file +jdbcDependency 'org.postgresql:postgresql:42.5.0' diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PGGeometryTypeDescriptor.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PGGeometryTypeDescriptor.java index d4a589789688..335db450c6db 100644 --- a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PGGeometryTypeDescriptor.java +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PGGeometryTypeDescriptor.java @@ -53,10 +53,11 @@ public Geometry toGeometry(Object object) { if ( object == null ) { return null; } - ByteBuffer buffer = null; + ByteBuffer buffer; if ( object instanceof PGobject ) { String pgValue = ( (PGobject) object ).getValue(); + assert pgValue != null; if ( pgValue.startsWith( "00" ) || pgValue.startsWith( "01" ) ) { //we have a WKB because this pgValue starts with the bit-order byte buffer = ByteBuffer.from( pgValue ); @@ -91,7 +92,7 @@ public ValueBinder getBinder(final JavaTypeDescriptor javaTypeDescript return new ValueBinder() { @Override - public final void bind(PreparedStatement st, X value, int index, WrapperOptions options) + public void bind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException { if ( value == null ) { st.setNull( index, Types.OTHER ); @@ -102,7 +103,7 @@ public final void bind(PreparedStatement st, X value, int index, WrapperOptions } @Override - public final void bind(CallableStatement st, X value, String name, WrapperOptions options) + public void bind(CallableStatement st, X value, String name, WrapperOptions options) throws SQLException { if ( value == null ) { st.setNull( name, Types.OTHER ); @@ -112,21 +113,21 @@ public final void bind(CallableStatement st, X value, String name, WrapperOption } } - protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) + private void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException { final PGobject obj = toPGobject( value, options ); st.setObject( index, obj ); } - protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) + private void doBind(CallableStatement st, X value, String name, WrapperOptions options) throws SQLException { final PGobject obj = toPGobject( value, options ); st.setObject( name, obj ); } private PGobject toPGobject(X value, WrapperOptions options) throws SQLException { - final WkbEncoder encoder = Wkb.newEncoder( Wkb.Dialect.POSTGIS_EWKB_1 ); - final Geometry geometry = javaTypeDescriptor.unwrap( value, Geometry.class, options ); + final WkbEncoder encoder = Wkb.newEncoder( wkbDialect ); + final Geometry geometry = javaTypeDescriptor.unwrap( value, Geometry.class, options ); final String hexString = encoder.encode( geometry, ByteOrder.NDR ).toString(); final PGobject obj = new PGobject(); obj.setType( "geometry" ); diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG82Dialect.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG82Dialect.java index 9351bceabb82..f93cb8aaf04c 100644 --- a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG82Dialect.java +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG82Dialect.java @@ -23,7 +23,7 @@ public class PostgisPG82Dialect extends PostgreSQL82Dialect implements SpatialDialect { - transient private PostgisSupport support = new PostgisSupport(); + final transient private PostgisSupport support = new PostgisSupport(); /** * Creates an instance @@ -45,7 +45,7 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry typeContributions, serviceRegistry ); - support.contributeTypes( typeContributions, serviceRegistry ); + support.contributeTypes( typeContributions, serviceRegistry, PGGeometryTypeDescriptor.INSTANCE_WKB_1 ); } @Override diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG91Dialect.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG91Dialect.java index f2577625f0b6..bc795f787300 100644 --- a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG91Dialect.java +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG91Dialect.java @@ -23,7 +23,7 @@ public class PostgisPG91Dialect extends PostgreSQL91Dialect implements SpatialDialect { - transient private PostgisSupport support = new PostgisSupport(); + final transient private PostgisSupport support = new PostgisSupport(); /** * Creates an instance @@ -45,7 +45,7 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry typeContributions, serviceRegistry ); - support.contributeTypes( typeContributions, serviceRegistry ); + support.contributeTypes( typeContributions, serviceRegistry, PGGeometryTypeDescriptor.INSTANCE_WKB_1 ); } @Override diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG92Dialect.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG92Dialect.java index e71f982f863b..dea76b6b45e6 100644 --- a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG92Dialect.java +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG92Dialect.java @@ -23,7 +23,7 @@ public class PostgisPG92Dialect extends PostgreSQL92Dialect implements SpatialDialect { - transient private PostgisSupport support = new PostgisSupport(); + final transient private PostgisSupport support = new PostgisSupport(); /** * Creates an instance @@ -45,7 +45,7 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry typeContributions, serviceRegistry ); - support.contributeTypes( typeContributions, serviceRegistry ); + support.contributeTypes( typeContributions, serviceRegistry, PGGeometryTypeDescriptor.INSTANCE_WKB_1 ); } @Override diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG93Dialect.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG93Dialect.java index ee4ddd988590..52ec43a3a8cc 100644 --- a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG93Dialect.java +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG93Dialect.java @@ -23,7 +23,7 @@ public class PostgisPG93Dialect extends PostgreSQL93Dialect implements SpatialDialect { - transient private PostgisSupport support = new PostgisSupport(); + final transient private PostgisSupport support = new PostgisSupport(); /** * Creates an instance @@ -45,7 +45,7 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry typeContributions, serviceRegistry ); - support.contributeTypes( typeContributions, serviceRegistry ); + support.contributeTypes( typeContributions, serviceRegistry, PGGeometryTypeDescriptor.INSTANCE_WKB_1 ); } @Override diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG94Dialect.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG94Dialect.java index 6c14a2afc2e1..972a6cdc1117 100644 --- a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG94Dialect.java +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG94Dialect.java @@ -23,7 +23,7 @@ public class PostgisPG94Dialect extends PostgreSQL94Dialect implements SpatialDialect { - transient private PostgisSupport support = new PostgisSupport(); + final transient private PostgisSupport support = new PostgisSupport(); /** * Creates an instance @@ -45,7 +45,7 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry typeContributions, serviceRegistry ); - support.contributeTypes( typeContributions, serviceRegistry ); + support.contributeTypes( typeContributions, serviceRegistry, PGGeometryTypeDescriptor.INSTANCE_WKB_1 ); } @Override diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG95Dialect.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG95Dialect.java index 1c31d221ad74..96101cec41ef 100644 --- a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG95Dialect.java +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG95Dialect.java @@ -25,7 +25,7 @@ public class PostgisPG95Dialect extends PostgreSQL95Dialect implements PGSpatial public PostgisPG95Dialect() { super(); registerColumnType( - PGGeometryTypeDescriptor.INSTANCE_WKB_1.getSqlType(), + PGGeometryTypeDescriptor.INSTANCE_WKB_2.getSqlType(), "GEOMETRY" ); for ( Map.Entry entry : functionsToRegister() ) { diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG9Dialect.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG9Dialect.java index 88fefa520530..939dbdf8b74e 100644 --- a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG9Dialect.java +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG9Dialect.java @@ -23,7 +23,7 @@ public class PostgisPG9Dialect extends PostgreSQL9Dialect implements SpatialDialect { - transient private PostgisSupport support = new PostgisSupport(); + final transient private PostgisSupport support = new PostgisSupport(); /** * Creates an instance @@ -31,7 +31,7 @@ public class PostgisPG9Dialect extends PostgreSQL9Dialect implements SpatialDial public PostgisPG9Dialect() { super(); registerColumnType( - PGGeometryTypeDescriptor.INSTANCE_WKB_1.getSqlType(), + PGGeometryTypeDescriptor.INSTANCE_WKB_2.getSqlType(), "GEOMETRY" ); for ( Map.Entry entry : support.functionsToRegister() ) { diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisSupport.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisSupport.java index ef361f47f5f0..c4f99bdd1c8f 100644 --- a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisSupport.java +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisSupport.java @@ -20,6 +20,7 @@ import org.hibernate.spatial.SpatialFunction; import org.hibernate.spatial.SpatialRelation; import org.hibernate.spatial.dialect.SpatialFunctionsRegistry; +import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; /** * Created by Karel Maesen, Geovise BVBA on 29/10/16. @@ -36,20 +37,28 @@ public PostgisSupport() { postgisFunctions = new PostgisFunctions(); } - public void contributeTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) { - typeContributions.contributeType( new GeolatteGeometryType( PGGeometryTypeDescriptor.INSTANCE_WKB_1 ) ); - typeContributions.contributeType( new JTSGeometryType( PGGeometryTypeDescriptor.INSTANCE_WKB_1 ) ); + public void contributeTypes( + TypeContributions typeContributions, + ServiceRegistry serviceRegistry, + SqlTypeDescriptor wkbType) { + typeContributions.contributeType( new GeolatteGeometryType( wkbType ) ); + typeContributions.contributeType( new JTSGeometryType( wkbType ) ); typeContributions.contributeJavaTypeDescriptor( GeolatteGeometryJavaTypeDescriptor.INSTANCE ); typeContributions.contributeJavaTypeDescriptor( JTSGeometryJavaTypeDescriptor.INSTANCE ); } + public void contributeTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) { + contributeTypes( typeContributions, serviceRegistry, PGGeometryTypeDescriptor.INSTANCE_WKB_2 ); + } + + public SpatialFunctionsRegistry functionsToRegister() { return postgisFunctions; } - public boolean isSpatial(int typeCode){ - return typeCode == Types.OTHER || typeCode == PGGeometryTypeDescriptor.INSTANCE_WKB_1.getSqlType(); + public boolean isSpatial(int typeCode) { + return typeCode == Types.OTHER || typeCode == PGGeometryTypeDescriptor.INSTANCE_WKB_2.getSqlType(); } /** @@ -117,17 +126,13 @@ public String getSpatialFilterExpression(String columnName) { */ @Override public String getSpatialAggregateSQL(String columnName, int aggregation) { - switch ( aggregation ) { - case SpatialAggregate.EXTENT: - final StringBuilder stbuf = new StringBuilder(); - stbuf.append( "st_extent(" ).append( columnName ).append( ")::geometry" ); - return stbuf.toString(); - default: - throw new IllegalArgumentException( - "Aggregation of type " - + aggregation + " are not supported by this dialect" - ); + if ( aggregation == SpatialAggregate.EXTENT ) { + return "st_extent(" + columnName + ")::geometry"; } + throw new IllegalArgumentException( + "Aggregation of type " + + aggregation + " are not supported by this dialect" + ); } /** diff --git a/hibernate-spatial/src/test/java/org/hibernate/spatial/dialect/postgis/TestWKBPostgis221.java b/hibernate-spatial/src/test/java/org/hibernate/spatial/dialect/postgis/TestWKBPostgis221.java new file mode 100644 index 000000000000..ee4f0427014f --- /dev/null +++ b/hibernate-spatial/src/test/java/org/hibernate/spatial/dialect/postgis/TestWKBPostgis221.java @@ -0,0 +1,73 @@ +/* + * 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.spatial.dialect.postgis; + +import java.util.List; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Before; +import org.junit.Test; + +import org.geolatte.geom.G2D; +import org.geolatte.geom.Point; + +import static org.geolatte.geom.builder.DSL.g; +import static org.geolatte.geom.builder.DSL.point; +import static org.geolatte.geom.crs.CoordinateReferenceSystems.WGS84; +import static org.junit.Assert.assertEquals; + + +@TestForIssue(jiraKey = "HHH-14932") +@RequiresDialect(PostgisPG95Dialect.class) +public class TestWKBPostgis221 extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Foo.class }; + } + + @Before + public void setup() { + inTransaction( session -> session.persist( new Foo( + 1, + point( WGS84 ) + ) ) ); + } + + @Test + public void test() { + inTransaction( session -> { + List list = session + .createQuery( "from Foo", Foo.class ) + .getResultList(); + assertEquals( point( WGS84 ), list.get( 0 ).point ); + } ); + } + + @Entity(name = "Foo") + @Table(name = "Foo") + public static class Foo { + @Id + long id; + Point point; + + public Foo() { + } + + public Foo(long id, Point point) { + this.id = id; + this.point = point; + } + + } +} diff --git a/hibernate-spatial/src/test/resources/hibernate.properties b/hibernate-spatial/src/test/resources/hibernate.properties index 6496030da17f..10a4341dc55e 100644 --- a/hibernate-spatial/src/test/resources/hibernate.properties +++ b/hibernate-spatial/src/test/resources/hibernate.properties @@ -12,6 +12,7 @@ hibernate.connection.driver_class=@jdbc.driver@ hibernate.connection.url=@jdbc.url@ hibernate.connection.username=@jdbc.user@ hibernate.connection.password=@jdbc.pass@ +hibernate.connection.init_sql @connection.init_sql@ #hibernate.cache.region_prefix hibernate.test #hibernate.cache.region.factory_class org.hibernate.testing.cache.CachingRegionFactory # diff --git a/hibernate-testing-jakarta/hibernate-testing-jakarta.gradle b/hibernate-testing-jakarta/hibernate-testing-jakarta.gradle index e734d0acbddd..de795aa1d217 100644 --- a/hibernate-testing-jakarta/hibernate-testing-jakarta.gradle +++ b/hibernate-testing-jakarta/hibernate-testing-jakarta.gradle @@ -1,3 +1,5 @@ +import javax.inject.Inject + /* * Hibernate, Relational Persistence for Idiomatic Java * @@ -10,9 +12,18 @@ description = 'Support for testing Hibernate ORM Jakarta functionality' apply from: rootProject.file( 'gradle/published-java-module.gradle' ) configurations { - jakartaeeTransformJars + jakartaeeTransformTool } +// we do not want the much of the normal java plugin's behavior +compileJava.enabled false +processResources.enabled false +compileTestJava.enabled false +processTestResources.enabled false +jar.enabled false +javadocJar.enabled false +sourcesJar.enabled false + dependencies { compile project( ':hibernate-core-jakarta' ) compile( libraries.jakarta_jta ) @@ -29,7 +40,7 @@ dependencies { transitive=false; } - jakartaeeTransformJars 'biz.aQute.bnd:biz.aQute.bnd.transform:5.1.1', + jakartaeeTransformTool 'biz.aQute.bnd:biz.aQute.bnd.transform:5.1.1', 'commons-cli:commons-cli:1.4', 'org.slf4j:slf4j-simple:1.7.30', 'org.slf4j:slf4j-api:1.7.26', @@ -70,10 +81,107 @@ jar { } javaexec { - classpath configurations.jakartaeeTransformJars + classpath configurations.jakartaeeTransformTool main = 'org.eclipse.transformer.jakarta.JakartaTransformer' args = transformerArgs } } } } + +task transformJar(type: JakartaJarTransformation) { + description 'Transforms the hibernate-testing jar using the JakartaTransformer tool' + + dependsOn project(':hibernate-testing').tasks.jar + mustRunAfter project(':hibernate-testing').tasks.jar + + sourceJar project(':hibernate-testing').tasks.jar.archiveFile + targetJar tasks.jar.archiveFile.get().asFile +} + +task transformSourcesJar(type: JakartaJarTransformation) { + description 'Transforms the hibernate-testing sources jar using the JakartaTransformer tool' + + dependsOn project(':hibernate-testing').tasks.sourcesJar + mustRunAfter project(':hibernate-testing').tasks.sourcesJar + + sourceJar project(':hibernate-testing').tasks.sourcesJar.archiveFile + targetJar tasks.sourcesJar.archiveFile.get().asFile +} + +task transformJavadocJar(type: JakartaJarTransformation) { + description 'Transforms the hibernate-testing javadoc jar using the JakartaTransformer tool' + + dependsOn project(':hibernate-testing').tasks.javadocJar + mustRunAfter project(':hibernate-testing').tasks.javadocJar + + sourceJar project(':hibernate-testing').tasks.javadocJar.archiveFile + targetJar tasks.javadocJar.archiveFile.get().asFile +} + +configurations { + [apiElements, runtimeElements].each { + it.outgoing.artifacts.removeIf { + it.buildDependencies.getDependencies(null).contains(jar) + } + it.outgoing.artifact(tasks.transformJar.targetJar) { + builtBy tasks.transformJar + } + it.outgoing.artifact(tasks.transformSourcesJar.targetJar) { + builtBy tasks.transformSourcesJar + } + it.outgoing.artifact(tasks.transformJavadocJar.targetJar) { + builtBy tasks.transformJavadocJar + } + } +} + +@CacheableTask +abstract class JakartaJarTransformation extends DefaultTask { + private final RegularFileProperty sourceJar; + private final RegularFileProperty targetJar; + + @Inject + JakartaJarTransformation(ObjectFactory objectFactory) { + sourceJar = objectFactory.fileProperty(); + targetJar = objectFactory.fileProperty(); + } + + @InputFile + @PathSensitive( PathSensitivity.RELATIVE ) + RegularFileProperty getSourceJar() { + return sourceJar; + } + + void sourceJar(Object fileReference) { + sourceJar.set( project.file( fileReference ) ) + } + + @OutputFile + RegularFileProperty getTargetJar() { + return targetJar; + } + + void targetJar(Object fileReference) { + targetJar.set( project.file( fileReference ) ) + } + + @TaskAction + void transform() { + project.javaexec( new Action() { + @Override + void execute(JavaExecSpec javaExecSpec) { + javaExecSpec.classpath( getProject().getConfigurations().getByName( "jakartaeeTransformTool" ) ); + javaExecSpec.setMain( "org.eclipse.transformer.jakarta.JakartaTransformer" ); + javaExecSpec.args( + sourceJar.get().getAsFile().getAbsolutePath(), + targetJar.get().getAsFile().getAbsolutePath(), + "-q", + "-tr", getProject().getRootProject().file( "rules/jakarta-renames.properties" ).getAbsolutePath(), + "-tv", getProject().getRootProject().file( "rules/jakarta-versions.properties" ).getAbsolutePath(), + "-td", getProject().getRootProject().file( "rules/jakarta-direct.properties" ).getAbsolutePath() + ); + } + }); + } +} diff --git a/hibernate-testing/hibernate-testing.gradle b/hibernate-testing/hibernate-testing.gradle index 0f39d04069a5..ecf47ebc8094 100644 --- a/hibernate-testing/hibernate-testing.gradle +++ b/hibernate-testing/hibernate-testing.gradle @@ -13,12 +13,22 @@ apply from: rootProject.file( 'gradle/published-java-module.gradle' ) dependencies { compile project( ':hibernate-core' ) compile( libraries.jta ) - compile( libraries.junit ) + + compile libraries.junit + compile libraries.junit5_api + compile libraries.junit5_params + + compile libraries.assertj + + compile libraries.log4j2 + + compile 'javax.money:money-api:1.0.1' + compile 'org.javamoney:moneta:1.1' + compile( libraries.byteman ) compile( libraries.byteman_install ) compile( libraries.byteman_bmunit ) compile( libraries.xapool ) - compile( libraries.log4j2 ) compile( libraries.jboss_tx_spi ) { transitive=false; } diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/boot/ExtraJavaServicesClassLoaderService.java b/hibernate-testing/src/main/java/org/hibernate/testing/boot/ExtraJavaServicesClassLoaderService.java new file mode 100644 index 000000000000..70dbfc66c1c4 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/boot/ExtraJavaServicesClassLoaderService.java @@ -0,0 +1,73 @@ +/* + * 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.testing.boot; + +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl; + +/** + * @author Steve Ebersole + */ +public class ExtraJavaServicesClassLoaderService extends ClassLoaderServiceImpl { + private final List> extraJavaServices; + + public ExtraJavaServicesClassLoaderService(List> extraJavaServices) { + this.extraJavaServices = extraJavaServices; + } + + @Override + public Collection loadJavaServices(Class serviceContract) { + final Collection baseServices = super.loadJavaServices( serviceContract ); + final List services = new ArrayList<>( baseServices ); + + applyExtraJavaServices( serviceContract, services ); + + return services; + } + + private void applyExtraJavaServices(Class serviceContract, List services) { + extraJavaServices.forEach( + (javaServiceDescriptor) -> { + if ( serviceContract.isAssignableFrom( javaServiceDescriptor.role ) ) { + try { + final Object serviceInstance = javaServiceDescriptor.impl.getDeclaredConstructor().newInstance(); + //noinspection unchecked + services.add( (S) serviceInstance ); + } + catch (NoSuchMethodException | IllegalAccessException e) { + throw new RuntimeException( "Unable to access constructor for specified 'extra' Java service : " + javaServiceDescriptor.impl.getName(), e ); + } + catch (InstantiationException | InvocationTargetException e) { + throw new RuntimeException( "Unable to instantiate specified 'extra' Java service : " + javaServiceDescriptor.impl.getName(), e ); + } + } + } + ); + } + + public static class JavaServiceDescriptor { + private final Class role; + private final Class impl; + + public JavaServiceDescriptor(Class role, Class impl) { + this.role = role; + this.impl = impl; + } + + public Class getRole() { + return role; + } + + public Class getImpl() { + return impl; + } + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/cleaner/SQLServerDatabaseCleaner.java b/hibernate-testing/src/main/java/org/hibernate/testing/cleaner/SQLServerDatabaseCleaner.java index 98ef6ef2d6c7..d149073c6f79 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/cleaner/SQLServerDatabaseCleaner.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/cleaner/SQLServerDatabaseCleaner.java @@ -54,22 +54,22 @@ public void clearAllSchemas(Connection c) { LOG.log( Level.FINEST, "Collect schema objects: START" ); rs = s.executeQuery( "SELECT 'ALTER TABLE [' + TABLE_SCHEMA + '].[' + TABLE_NAME + '] DROP CONSTRAINT [' + CONSTRAINT_NAME + ']' FROM INFORMATION_SCHEMA.CONSTRAINT_TABLE_USAGE " + - "WHERE EXISTS (SELECT 1 FROM sys.Tables t JOIN sys.Schemas s ON t.schema_id = s.schema_id WHERE t.is_ms_shipped = 0 AND s.name = TABLE_SCHEMA AND t.name = TABLE_NAME) " + - "AND EXISTS (SELECT 1 FROM sys.Foreign_keys WHERE name = CONSTRAINT_NAME)" ); + "WHERE EXISTS (SELECT 1 FROM sys.tables t JOIN sys.schemas s ON t.schema_id = s.schema_id WHERE t.is_ms_shipped = 0 AND s.name = TABLE_SCHEMA AND t.name = TABLE_NAME) " + + "AND EXISTS (SELECT 1 FROM sys.foreign_keys WHERE name = CONSTRAINT_NAME)" ); while ( rs.next() ) { sqls.add( rs.getString( 1 ) ); } rs = s.executeQuery( "SELECT 'DROP VIEW [' + TABLE_SCHEMA + '].[' + TABLE_NAME + ']' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'VIEW' " + - "AND EXISTS (SELECT 1 FROM sys.Views t JOIN sys.Schemas s ON t.schema_id = s.schema_id WHERE t.is_ms_shipped = 0 AND s.name = TABLE_SCHEMA AND t.name = TABLE_NAME)" ); + "AND EXISTS (SELECT 1 FROM sys.views t JOIN sys.schemas s ON t.schema_id = s.schema_id WHERE t.is_ms_shipped = 0 AND s.name = TABLE_SCHEMA AND t.name = TABLE_NAME)" ); while ( rs.next() ) { sqls.add( rs.getString( 1 ) ); } rs = s.executeQuery( "SELECT 'DROP TABLE [' + TABLE_SCHEMA + '].[' + TABLE_NAME + ']' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' " + - "AND EXISTS (SELECT 1 FROM sys.Tables t JOIN sys.Schemas s ON t.schema_id = s.schema_id WHERE t.is_ms_shipped = 0 AND s.name = TABLE_SCHEMA AND t.name = TABLE_NAME)" ); + "AND EXISTS (SELECT 1 FROM sys.tables t JOIN sys.schemas s ON t.schema_id = s.schema_id WHERE t.is_ms_shipped = 0 AND s.name = TABLE_SCHEMA AND t.name = TABLE_NAME)" ); while ( rs.next() ) { sqls.add( rs.getString( 1 ) ); } diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/env/ConnectionProviderBuilder.java b/hibernate-testing/src/main/java/org/hibernate/testing/env/ConnectionProviderBuilder.java index 8a793590168f..1dad95c83d10 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/env/ConnectionProviderBuilder.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/env/ConnectionProviderBuilder.java @@ -44,6 +44,7 @@ public static Properties getConnectionProviderProperties(String dbName) { props.put( Environment.URL, String.format( URL_FORMAT, dbName ) ); props.put( Environment.USER, USER ); props.put( Environment.PASS, PASS ); + props.put( "hibernate.connection.init_sql", "" ); return props; } @@ -53,6 +54,7 @@ public static Properties getJpaConnectionProviderProperties(String dbName) { props.put( Environment.JPA_JDBC_URL, String.format( URL_FORMAT, dbName ) ); props.put( Environment.JPA_JDBC_USER, USER ); props.put( Environment.JPA_JDBC_PASSWORD, PASS ); + props.put( "hibernate.connection.init_sql", "" ); return props; } diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/jdbc/SQLStatementInspector.java b/hibernate-testing/src/main/java/org/hibernate/testing/jdbc/SQLStatementInspector.java new file mode 100644 index 000000000000..0777b67ff955 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/jdbc/SQLStatementInspector.java @@ -0,0 +1,119 @@ +/* + * 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.testing.jdbc; + +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; + +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.resource.jdbc.spi.StatementInspector; +import org.hibernate.sql.JoinType; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import org.assertj.core.api.Assertions; + +/** + * @author Andrea Boriero + */ +public class SQLStatementInspector implements StatementInspector { + private final List sqlQueries = new LinkedList<>(); + + public SQLStatementInspector() { + } + + @Override + public String inspect(String sql) { + sqlQueries.add( sql ); + return sql; + } + + public List getSqlQueries() { + return sqlQueries; + } + + public void clear() { + sqlQueries.clear(); + } + + public int getNumberOfJoins(int position) { + final String sql = sqlQueries.get( position ); + String fromPart = sql.toLowerCase( Locale.ROOT ).split( " from " )[1].split( " where " )[0]; + return fromPart.split( "(\\sjoin\\s|,\\s)", -1 ).length - 1; + } + + public void assertExecuted(String expected) { + assertTrue( sqlQueries.contains( expected ) ); + } + + public void assertNumberOfJoins(int queryNumber, int expectedNumberOfJoins) { + assertNumberOfOccurrenceInQuery( queryNumber, "join", expectedNumberOfJoins ); + } + + public void assertExecutedCount(int expected) { + assertEquals( "Number of executed statements ",expected, sqlQueries.size() ); + } + + public void assertNumberOfJoins(int queryNumber, JoinType joinType, int expectedNumberOfOccurrences) { + String query = sqlQueries.get( queryNumber ); + String[] parts = query.split( " join " ); + int actual = getCount( parts, joinType ); + assertThat( "number of " + joinType + "join", actual, is( expectedNumberOfOccurrences ) ); + } + + private int getCount(String[] parts, JoinType joinType) { + final int end = parts.length - 1; + int count = 0; + for ( int i = 0; i < end; i++ ) { + if ( parts[i].endsWith( " " + joinType.getSqlText() ) ) { + count++; + } + } + return count; + } + + public void assertNumberOfOccurrenceInQuery(int queryNumber, String toCheck, int expectedNumberOfOccurrences) { + String query = sqlQueries.get( queryNumber ); + int actual = query.split( " " + toCheck + " ", -1 ).length - 1; + assertThat( "number of " + toCheck, actual, is( expectedNumberOfOccurrences ) ); + } + + public void assertIsSelect(int queryNumber) { + String query = sqlQueries.get( queryNumber ); + assertTrue( query.toLowerCase( Locale.ROOT ).startsWith( "select" ) ); + } + + public void assertIsInsert(int queryNumber) { + String query = sqlQueries.get( queryNumber ); + assertTrue( query.toLowerCase( Locale.ROOT ).startsWith( "insert" ) ); + } + + public void assertIsUpdate(int queryNumber) { + String query = sqlQueries.get( queryNumber ); + assertTrue( query.toLowerCase( Locale.ROOT ).startsWith( "update" ) ); + } + + public void assertNoUpdate() { + Assertions.assertThat( sqlQueries ) + .isNotEmpty() + .allSatisfy( sql -> Assertions.assertThat( sql.toLowerCase( Locale.ROOT ) ).doesNotStartWith( "update" ) ); + } + + public void assertUpdate() { + Assertions.assertThat( sqlQueries ) + .isNotEmpty() + .anySatisfy( sql -> Assertions.assertThat( sql.toLowerCase( Locale.ROOT ) ).startsWith( "update" ) ); + } + + public static SQLStatementInspector extractFromSession(SessionImplementor session) { + return (SQLStatementInspector) session.getJdbcSessionContext().getStatementInspector(); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/jdbc/SharedDriverManagerConnectionProviderImpl.java b/hibernate-testing/src/main/java/org/hibernate/testing/jdbc/SharedDriverManagerConnectionProviderImpl.java index 28ba052668db..143e6756a164 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/jdbc/SharedDriverManagerConnectionProviderImpl.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/jdbc/SharedDriverManagerConnectionProviderImpl.java @@ -31,6 +31,7 @@ public static SharedDriverManagerConnectionProviderImpl getInstance() { } private Config config; + private Boolean supportsIsValid; @Override public void configure(Map configurationValues) { @@ -46,8 +47,23 @@ public void configure(Map configurationValues) { @Override public boolean isValid(Connection connection) throws SQLException { - // Wait at most 5 seconds to validate a connection is still valid - return connection.isValid( 5 ); + if ( supportsIsValid == Boolean.FALSE ) { + // Assume is valid if the driver doesn't support the check + return true; + } + Boolean supportsIsValid = Boolean.FALSE; + try { + // Wait at most 5 seconds to validate a connection is still valid + boolean valid = connection.isValid( 5 ); + supportsIsValid = Boolean.TRUE; + return valid; + } + catch (AbstractMethodError e) { + return true; + } + finally { + this.supportsIsValid = supportsIsValid; + } } @Override @@ -58,7 +74,6 @@ public void stop() { public void reset() { super.stop(); - config = null; } private static class Config { @@ -71,7 +86,7 @@ private static class Config { private final Properties connectionProps; private final Integer isolation; - public Config(Map configurationValues) { + public Config(Map configurationValues) { this.autoCommit = ConfigurationHelper.getBoolean( AvailableSettings.AUTOCOMMIT, configurationValues, false ); this.minSize = ConfigurationHelper.getInt( MIN_SIZE, configurationValues, 2 ); this.maxSize = ConfigurationHelper.getInt( AvailableSettings.POOL_SIZE, configurationValues, 20 ); diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/logger/LogInspectionHelper.java b/hibernate-testing/src/main/java/org/hibernate/testing/logger/LogInspectionHelper.java index a81e78bbf9a7..2892991d0795 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/logger/LogInspectionHelper.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/logger/LogInspectionHelper.java @@ -19,16 +19,16 @@ * * @author Sanne Grinovero (C) 2015 Red Hat Inc. */ -final class LogInspectionHelper { +public final class LogInspectionHelper { private LogInspectionHelper() { } - static void registerListener(LogListener listener, BasicLogger log) { + public static void registerListener(LogListener listener, BasicLogger log) { convertType( log ).registerListener( listener ); } - static void clearAllListeners(BasicLogger log) { + public static void clearAllListeners(BasicLogger log) { convertType( log ).clearAllListeners(); } diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/AbstractDomainModelDescriptor.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/AbstractDomainModelDescriptor.java new file mode 100644 index 000000000000..f09692aecbae --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/AbstractDomainModelDescriptor.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.testing.orm.domain; + +import org.hibernate.boot.MetadataSources; + +/** + * Convenience base class test domain models based on annotated classes + * + * @author Steve Ebersole + */ +public abstract class AbstractDomainModelDescriptor implements DomainModelDescriptor { + private final Class[] annotatedClasses; + + protected AbstractDomainModelDescriptor(Class... annotatedClasses) { + this.annotatedClasses = annotatedClasses; + } + + @Override + public Class[] getAnnotatedClasses() { + return annotatedClasses; + } + + @Override + public void applyDomainModel(MetadataSources sources) { + for ( Class annotatedClass : annotatedClasses ) { + sources.addAnnotatedClass( annotatedClass ); + } + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/DomainModelDescriptor.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/DomainModelDescriptor.java new file mode 100644 index 000000000000..4d5eb852ea07 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/DomainModelDescriptor.java @@ -0,0 +1,62 @@ +/* + * 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.testing.orm.domain; + +import java.util.EnumSet; + +import org.hibernate.boot.MetadataSources; +import org.hibernate.dialect.Dialect; + +/** + * Describes a standard domain model + * + * @see StandardDomainModel + * @see org.hibernate.testing.orm.junit.DomainModel + * @see org.hibernate.testing.orm.junit.DomainModelFunctionalTesting + * @see org.hibernate.testing.orm.junit.DomainModelExtension + * + * @author Steve Ebersole + */ +public interface DomainModelDescriptor { + + Class[] getAnnotatedClasses(); + + /** + * Apply the model classes to the given MetadataSources + */ + void applyDomainModel(MetadataSources sources); + + /** + * The namespace to apply the model to. This is interpreted as a catalog + * name or a schema name depending on the capability of the underlying database + * via {@link Dialect}. Would require a new Dialect method I think, though + * we could also leverage the driver's db-metadata to ascertain which interpretation + * to use which would not need any (more) test-specific Dialect feature. + * + * Note however that this might be a useful feature as well for users instead of + * JPA's {@link javax.persistence.Table#catalog} / {@link javax.persistence.Table#schema}. + * AKA, something like `@org.hibernate.annotations.Namespace("a_name")` or + * `@org.hibernate.annotations.Table( namespace="a_name", ... )`. + * + * This may be {@code null} indicating that the default namespace should be used. + * + * Note that domain models can use the same namespace so long as they do not share + * db-object (tables, etc) names + */ + default String getNamespace() { + return null; + } + + /** + * Identifies the specific mapping features this domain model uses. + */ + default EnumSet getMappingFeaturesUsed() { + // for now just return none. this is simply informative, not used to + // drive any functionality - so maybe it's not important to add + return EnumSet.noneOf( MappingFeature.class ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/MappingFeature.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/MappingFeature.java new file mode 100644 index 000000000000..536992fa6ce7 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/MappingFeature.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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain; + +import java.util.EnumSet; + +/** + * Identifies specific mapping features used by a {@link DomainModelDescriptor}. + * + * The intent is to help categorize which models use specific mapping features + * to help facilitate testing various outcomes based on those features. + * + * For example, when writing a test that depends on JPA's {@link javax.persistence.AttributeConverter}, + * we could just see which DomainModel reports using {@link #CONVERTER} and re-use that + * model. + * + * @author Steve Ebersole + */ +public enum MappingFeature { + CONVERTER, + ENUMERATED, + DYNAMIC_MODEL, + + DISCRIMINATOR_INHERIT, + JOINED_INHERIT, + UNION_INHERIT, + + SECONDARY_TABLE, + + AGG_COMP_ID, + NON_AGG_COMP_ID, + ID_CLASS, + + EMBEDDABLE, + MANY_ONE, + ONE_ONE, + ONE_MANY, + MANY_MANY, + ANY, + MANY_ANY, + + COLLECTION_TABLE, + JOIN_TABLE, + JOIN_COLUMN, + + ; + + public static EnumSet all() { + return EnumSet.allOf( MappingFeature.class ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/MonetaryAmountConverter.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/MonetaryAmountConverter.java new file mode 100644 index 000000000000..b529f2bc7778 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/MonetaryAmountConverter.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.testing.orm.domain; + +import java.util.Locale; +import javax.money.Monetary; +import javax.money.MonetaryAmount; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +/** + * @author Steve Ebersole + */ +@Converter( autoApply = true ) +public class MonetaryAmountConverter implements AttributeConverter { + @Override + public Double convertToDatabaseColumn(MonetaryAmount attribute) { + return attribute.getNumber().numberValueExact( Double.class ); + } + + @Override + public MonetaryAmount convertToEntityAttribute(Double dbData) { + if ( dbData == null ) { + return null; + } + + return Monetary.getDefaultAmountFactory().setNumber( dbData ).setCurrency( Monetary.getCurrency( Locale.US ) ).create(); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/StandardDomainModel.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/StandardDomainModel.java new file mode 100644 index 000000000000..f5b3e67d5dca --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/StandardDomainModel.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.testing.orm.domain; + +import org.hibernate.testing.orm.domain.animal.AnimalDomainModel; +import org.hibernate.testing.orm.domain.contacts.ContactsDomainModel; +import org.hibernate.testing.orm.domain.gambit.GambitDomainModel; +import org.hibernate.testing.orm.domain.helpdesk.HelpDeskDomainModel; +import org.hibernate.testing.orm.domain.retail.RetailDomainModel; + +/** + * @author Steve Ebersole + */ +public enum StandardDomainModel { + CONTACTS( ContactsDomainModel.INSTANCE ), + ANIMAL( AnimalDomainModel.INSTANCE ), + GAMBIT( GambitDomainModel.INSTANCE ), + HELPDESK( HelpDeskDomainModel.INSTANCE ), + RETAIL( RetailDomainModel.INSTANCE ); + + private final DomainModelDescriptor domainModel; + + StandardDomainModel(DomainModelDescriptor domainModel) { + this.domainModel = domainModel; + } + + public DomainModelDescriptor getDescriptor() { + return domainModel; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Address.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Address.java new file mode 100644 index 000000000000..fd187cb2f873 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Address.java @@ -0,0 +1,111 @@ +/* + * 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.testing.orm.domain.animal; + +import javax.persistence.Embeddable; + +@Embeddable +public class Address { + private String street; + private String city; + private String postalCode; + private String country; +// private StateProvince stateProvince; + + public String getStreet() { + return street; + } + + public void setStreet(String street) { + this.street = street; + } + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + + public String getPostalCode() { + return postalCode; + } + + public void setPostalCode(String postalCode) { + this.postalCode = postalCode; + } + + public String getCountry() { + return country; + } + + public void setCountry(String country) { + this.country = country; + } + +// @ManyToOne +// @JoinColumn( name = "state_prov_fk" ) +// public StateProvince getStateProvince() { +// return stateProvince; +// } +// +// public void setStateProvince(StateProvince stateProvince) { +// this.stateProvince = stateProvince; +// } + +// @Override +// public boolean equals(Object o) { +// if ( this == o ) { +// return true; +// } +// if ( o == null || getClass() != o.getClass() ) { +// return false; +// } +// +// Address address = ( Address ) o; +// +// if ( city != null ? !city.equals( address.city ) : address.city != null ) { +// return false; +// } +// if ( country != null ? !country.equals( address.country ) : address.country != null ) { +// return false; +// } +// if ( postalCode != null ? !postalCode.equals( address.postalCode ) : address.postalCode != null ) { +// return false; +// } +// if ( stateProvince != null ? !stateProvince.equals( address.stateProvince ) : address.stateProvince != null ) { +// return false; +// } +// if ( street != null ? !street.equals( address.street ) : address.street != null ) { +// return false; +// } +// +// return true; +// } +// +// @Override +// public int hashCode() { +// int result = street != null ? street.hashCode() : 0; +// result = 31 * result + ( city != null ? city.hashCode() : 0 ); +// result = 31 * result + ( postalCode != null ? postalCode.hashCode() : 0 ); +// result = 31 * result + ( country != null ? country.hashCode() : 0 ); +// result = 31 * result + ( stateProvince != null ? stateProvince.hashCode() : 0 ); +// return result; +// } +// +// @Override +// public String toString() { +// return "Address{" + +// "street='" + street + '\'' + +// ", city='" + city + '\'' + +// ", postalCode='" + postalCode + '\'' + +// ", country='" + country + '\'' + +// ", stateProvince=" + stateProvince + +// '}'; +// } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Animal.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Animal.java new file mode 100644 index 000000000000..fd5b6f4d6fd4 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Animal.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.testing.orm.domain.animal; + +import java.util.HashSet; +import java.util.Set; + +import javax.persistence.Column; +import javax.persistence.Entity; +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.OrderBy; + +@Entity +@Inheritance( strategy = InheritanceType.JOINED ) +public class Animal { + private Long id; + private float bodyWeight; + private Set offspring; + private Animal mother; + private Animal father; + private String description; + private Zoo zoo; + private String serialNumber; + + public Animal() { + } + + public Animal(String description, float bodyWeight) { + this.description = description; + this.bodyWeight = bodyWeight; + } + + @Id + 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; + } + + @Column( name = "body_weight" ) + public float getBodyWeight() { + return bodyWeight; + } + + public void setBodyWeight(float bodyWeight) { + this.bodyWeight = bodyWeight; + } + + public String getSerialNumber() { + return serialNumber; + } + + public void setSerialNumber(String serialNumber) { + this.serialNumber = serialNumber; + } + + @ManyToOne + @JoinColumn( name = "zoo_fk" ) + public Zoo getZoo() { + return zoo; + } + + public void setZoo(Zoo zoo) { + this.zoo = zoo; + } + + @ManyToOne + @JoinColumn( name = "mother_fk" ) + public Animal getMother() { + return mother; + } + + public void setMother(Animal mother) { + this.mother = mother; + } + + @ManyToOne + @JoinColumn( name = "father_fk" ) + public Animal getFather() { + return father; + } + + public void setFather(Animal father) { + this.father = father; + } + + @OneToMany + @JoinColumn( name = "mother_fk" ) + @OrderBy( "father_fk" ) + public Set getOffspring() { + return offspring; + } + + public void addOffspring(Animal offspring) { + if ( this.offspring == null ) { + this.offspring = new HashSet(); + } + + this.offspring.add( offspring ); + } + + public void setOffspring(Set offspring) { + this.offspring = offspring; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/AnimalDomainModel.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/AnimalDomainModel.java new file mode 100644 index 000000000000..d8fdf2250f87 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/AnimalDomainModel.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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.animal; + +import org.hibernate.boot.MetadataSources; + +import org.hibernate.testing.orm.domain.AbstractDomainModelDescriptor; + +/** + * @author Steve Ebersole + */ +public class AnimalDomainModel extends AbstractDomainModelDescriptor { + /** + * Singleton access + */ + public static final AnimalDomainModel INSTANCE = new AnimalDomainModel(); + + public static void applyContactsModel(MetadataSources sources) { + INSTANCE.applyDomainModel( sources ); + } + + public AnimalDomainModel() { + super( + Address.class, + Animal.class, + Cat.class, + Classification.class, + Dog.class, + DomesticAnimal.class, + Human.class, + Lizard.class, + Mammal.class, + Name.class, + PettingZoo.class, + Reptile.class, + StateProvince.class, + Zoo.class + ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Cat.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Cat.java new file mode 100644 index 000000000000..dc2419eb5db6 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Cat.java @@ -0,0 +1,16 @@ +/* + * 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.testing.orm.domain.animal; + +import javax.persistence.Entity; +import javax.persistence.PrimaryKeyJoinColumn; + +@Entity +@PrimaryKeyJoinColumn( name = "cat_id_fk" ) +public class Cat extends DomesticAnimal { + +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Classification.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Classification.java new file mode 100644 index 000000000000..429f2b5c9cba --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Classification.java @@ -0,0 +1,28 @@ +/* + * 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.testing.orm.domain.animal; + +/** + * Mimic a JDK 5 enum. + * + * @author Steve Ebersole + */ +public enum Classification implements Comparable { + COOL, + LAME; + + public static Classification valueOf(Integer ordinal) { + if ( ordinal == null ) { + return null; + } + switch ( ordinal ) { + case 0: return COOL; + case 1: return LAME; + default: throw new IllegalArgumentException( "unknown classification ordinal [" + ordinal + "]" ); + } + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Dog.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Dog.java new file mode 100644 index 000000000000..908ef4461bfa --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Dog.java @@ -0,0 +1,16 @@ +/* + * 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.testing.orm.domain.animal; + +import javax.persistence.Entity; +import javax.persistence.PrimaryKeyJoinColumn; + +@Entity +@PrimaryKeyJoinColumn( name = "dog_id_fk" ) +public class Dog extends DomesticAnimal { + +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/DomesticAnimal.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/DomesticAnimal.java new file mode 100644 index 000000000000..d7d4cfecbdb5 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/DomesticAnimal.java @@ -0,0 +1,28 @@ +/* + * 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.testing.orm.domain.animal; + +import javax.persistence.Entity; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.PrimaryKeyJoinColumn; + +@Entity +@PrimaryKeyJoinColumn( name = "domestic_animal_id_fk" ) +public class DomesticAnimal extends Mammal { + private Human owner; + + @ManyToOne + @JoinColumn( name = "owner_fk" ) + public Human getOwner() { + return owner; + } + + public void setOwner(Human owner) { + this.owner = owner; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Human.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Human.java new file mode 100644 index 000000000000..3e018a903b1a --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Human.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 . + */ +package org.hibernate.testing.orm.domain.animal; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import org.hibernate.annotations.ColumnTransformer; +import org.hibernate.annotations.SortNatural; + +import javax.persistence.CollectionTable; +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Embedded; +import javax.persistence.Entity; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; +import javax.persistence.MapKeyColumn; +import javax.persistence.OneToMany; +import javax.persistence.PrimaryKeyJoinColumn; + +@Entity +@PrimaryKeyJoinColumn( name = "human_id_fk" ) +public class Human extends Mammal { + private Name name; + private String nickName; + private double heightInches; + + private BigInteger bigIntegerValue; + private BigDecimal bigDecimalValue; + private int intValue; + private float floatValue; + + private Collection friends; + private Collection pets; + private Map family; + private Set nickNames; + private Map addresses; + + @Embedded + public Name getName() { + return name; + } + + public void setName(Name name) { + this.name = name; + } + + public String getNickName() { + return nickName; + } + + public void setNickName(String nickName) { + this.nickName = nickName; + } + + @Column( name = "height_centimeters", nullable = false ) + @ColumnTransformer( read = "height_centimeters / 2.54E0", write = "? * 2.54E0" ) + public double getHeightInches() { + return heightInches; + } + + public void setHeightInches(double height) { + this.heightInches = height; + } + + public BigDecimal getBigDecimalValue() { + return bigDecimalValue; + } + + public void setBigDecimalValue(BigDecimal bigDecimalValue) { + this.bigDecimalValue = bigDecimalValue; + } + + public BigInteger getBigIntegerValue() { + return bigIntegerValue; + } + + public void setBigIntegerValue(BigInteger bigIntegerValue) { + this.bigIntegerValue = bigIntegerValue; + } + + public float getFloatValue() { + return floatValue; + } + + public void setFloatValue(float floatValue) { + this.floatValue = floatValue; + } + + public int getIntValue() { + return intValue; + } + + public void setIntValue(int intValue) { + this.intValue = intValue; + } + + @ElementCollection + @CollectionTable( name = "human_nick_names", joinColumns = @JoinColumn( name = "human_fk" ) ) + @Column( name = "nick_name" ) + @SortNatural + public Set getNickNames() { + return nickNames; + } + + public void setNickNames(Set nickNames) { + this.nickNames = nickNames; + } + + @ManyToMany + @JoinTable( + name = "friends", + joinColumns = @JoinColumn( name = "friend_fk1" ), + inverseJoinColumns = @JoinColumn( name = "friend_fk2" ) + ) + public Collection getFriends() { + return friends; + } + + public void setFriends(Collection friends) { + this.friends = friends; + } + + @OneToMany( mappedBy = "owner" ) + public Collection getPets() { + return pets; + } + + public void setPets(Collection pets) { + this.pets = pets; + } + + @ManyToMany + @JoinTable( + name = "family", + joinColumns = @JoinColumn( name = "family_fk1" ), + inverseJoinColumns = @JoinColumn( name = "family_fk2" ) + ) + @MapKeyColumn( name = "relationship" ) + public Map getFamily() { + return family; + } + + + public void setFamily(Map family) { + this.family = family; + } + + @ElementCollection + @CollectionTable( name = "human_addresses", joinColumns = @JoinColumn( name = "human_fk" ) ) + @MapKeyColumn( name = "`type`" ) + public Map getAddresses() { + return addresses; + } + + public void setAddresses(Map addresses) { + this.addresses = addresses; + } + + +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Lizard.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Lizard.java new file mode 100644 index 000000000000..fc73da12a964 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Lizard.java @@ -0,0 +1,15 @@ +/* + * 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.testing.orm.domain.animal; + +import javax.persistence.Entity; +import javax.persistence.PrimaryKeyJoinColumn; + +@Entity +@PrimaryKeyJoinColumn( name = "lizard_id_fk" ) +public class Lizard extends Reptile { +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Mammal.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Mammal.java new file mode 100644 index 000000000000..240a1700a1e0 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Mammal.java @@ -0,0 +1,67 @@ +/* + * 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.testing.orm.domain.animal; + +import java.util.Date; + +import javax.persistence.Entity; +import javax.persistence.PrimaryKeyJoinColumn; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +@Entity +@PrimaryKeyJoinColumn( name = "mammal_id_fk" ) +public class Mammal extends Animal { + private boolean pregnant; + private Date birthdate; + + public boolean isPregnant() { + return pregnant; + } + + public void setPregnant(boolean pregnant) { + this.pregnant = pregnant; + } + + @Temporal( TemporalType.DATE ) + public Date getBirthdate() { + return birthdate; + } + + + public void setBirthdate(Date birthdate) { + this.birthdate = birthdate; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( !( o instanceof Mammal ) ) { + return false; + } + + Mammal mammal = ( Mammal ) o; + + if ( pregnant != mammal.pregnant ) { + return false; + } + if ( birthdate != null ? !birthdate.equals( mammal.birthdate ) : mammal.birthdate != null ) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = ( pregnant ? 1 : 0 ); + result = 31 * result + ( birthdate != null ? birthdate.hashCode() : 0 ); + return result; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Name.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Name.java new file mode 100644 index 000000000000..834dd96d48e6 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Name.java @@ -0,0 +1,56 @@ +/* + * 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.testing.orm.domain.animal; + +import javax.persistence.Column; +import javax.persistence.Embeddable; + +@Embeddable +public class Name { + private String first; + private Character initial; + private String last; + + public Name() {} + + public Name(String first, Character initial, String last) { + this.first = first; + this.initial = initial; + this.last = last; + } + + public Name(String first, char initial, String last) { + this( first, Character.valueOf( initial ), last ); + } + + @Column( name = "name_first" ) + public String getFirst() { + return first; + } + + public void setFirst(String first) { + this.first = first; + } + + @Column( name = "name_initial" ) + public Character getInitial() { + return initial; + } + + public void setInitial(Character initial) { + this.initial = initial; + } + + @Column( name = "name_last" ) + public String getLast() { + return last; + } + + public void setLast(String last) { + this.last = last; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/PettingZoo.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/PettingZoo.java new file mode 100644 index 000000000000..7a35f65ec521 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/PettingZoo.java @@ -0,0 +1,16 @@ +/* + * 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.testing.orm.domain.animal; + +import javax.persistence.DiscriminatorValue; +import javax.persistence.Entity; + +@Entity +@DiscriminatorValue( "P" ) +public class PettingZoo extends Zoo { + +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Reptile.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Reptile.java new file mode 100644 index 000000000000..709236df6445 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Reptile.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 . + */ +package org.hibernate.testing.orm.domain.animal; + +import javax.persistence.Entity; +import javax.persistence.PrimaryKeyJoinColumn; + +@Entity +@PrimaryKeyJoinColumn( name = "reptile_id_fk" ) +public class Reptile extends Animal { + private float bodyTemperature; + public float getBodyTemperature() { + return bodyTemperature; + } + public void setBodyTemperature(float bodyTemperature) { + this.bodyTemperature = bodyTemperature; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/StateProvince.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/StateProvince.java new file mode 100644 index 000000000000..dd575f842c6b --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/StateProvince.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 . + */ +package org.hibernate.testing.orm.domain.animal; + +import javax.persistence.Entity; +import javax.persistence.Id; + +@Entity +public class StateProvince { + private Long id; + private String name; + private String isoCode; + + @Id + 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 String getIsoCode() { + return isoCode; + } + + public void setIsoCode(String isoCode) { + this.isoCode = isoCode; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( !( o instanceof StateProvince ) ) { + return false; + } + + StateProvince that = ( StateProvince ) o; + + if ( isoCode != null ? !isoCode.equals( that.getIsoCode() ) : that.getIsoCode() != null ) { + return false; + } + if ( name != null ? !name.equals( that.getName() ) : that.getName() != null ) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = name != null ? name.hashCode() : 0; + result = 31 * result + ( isoCode != null ? isoCode.hashCode() : 0 ); + return result; + } + + @Override + public String toString() { + return "StateProvince{" + + "id=" + id + + ", name='" + name + '\'' + + ", isoCode='" + isoCode + '\'' + + '}'; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Zoo.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Zoo.java new file mode 100644 index 000000000000..30096faf7737 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Zoo.java @@ -0,0 +1,145 @@ +/* + * 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.testing.orm.domain.animal; + +import java.util.HashMap; +import java.util.Map; + +import javax.persistence.DiscriminatorColumn; +import javax.persistence.DiscriminatorValue; +import javax.persistence.Embedded; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; +import javax.persistence.MapKeyColumn; +import javax.persistence.OneToMany; + +@Entity +@Inheritance +@DiscriminatorColumn( name = "zooType" ) +@DiscriminatorValue( "Z" ) +public class Zoo { + private Long id; + private String name; + private Classification classification; + private Map directors = new HashMap(); + private Map animals = new HashMap(); + private Map mammals = new HashMap(); + private Address address; + + public Zoo() { + } + + public Zoo(String name, Address address) { + this.name = name; + this.address = address; + } + + @Id + 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; + } + + @ManyToMany + @JoinTable( + name = "t_directors", + joinColumns = @JoinColumn( name = "zoo_fk" ), + inverseJoinColumns = @JoinColumn( name = "director_fk" ) + ) + @MapKeyColumn( name = "`title`" ) + public Map getDirectors() { + return directors; + } + + public void setDirectors(Map directors) { + this.directors = directors; + } + + @OneToMany + @JoinColumn( name = "mammal_fk" ) + @MapKeyColumn( name = "name" ) + public Map getMammals() { + return mammals; + } + + public void setMammals(Map mammals) { + this.mammals = mammals; + } + + @OneToMany( mappedBy = "zoo" ) + @MapKeyColumn( name = "serialNumber" ) + public Map getAnimals() { + return animals; + } + + public void setAnimals(Map animals) { + this.animals = animals; + } + + @Embedded + public Address getAddress() { + return address; + } + + public void setAddress(Address address) { + this.address = address; + } + + @Enumerated( value = EnumType.STRING ) + public Classification getClassification() { + return classification; + } + + public void setClassification(Classification classification) { + this.classification = classification; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( !( o instanceof Zoo ) ) { + return false; + } + + Zoo zoo = ( Zoo ) o; + + if ( address != null ? !address.equals( zoo.address ) : zoo.address != null ) { + return false; + } + if ( name != null ? !name.equals( zoo.name ) : zoo.name != null ) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = name != null ? name.hashCode() : 0; + result = 31 * result + ( address != null ? address.hashCode() : 0 ); + return result; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/package-info.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/package-info.java new file mode 100644 index 000000000000..5a0624d85dae --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/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 + */ + +/** + * Standard model for Hibernate's legacy Animal model used in HQL testing + */ +package org.hibernate.testing.orm.domain.animal; diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/contacts/Address.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/contacts/Address.java new file mode 100644 index 000000000000..229619abaf8a --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/contacts/Address.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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.contacts; + +import javax.persistence.Embeddable; + +/** + * @author Steve Ebersole + */ +@Embeddable +public class Address { + private Classification classification; + private String line1; + private String line2; + private PostalCode postalCode; + + public Classification getClassification() { + return classification; + } + + public void setClassification(Classification classification) { + this.classification = classification; + } + + public String getLine1() { + return line1; + } + + public void setLine1(String line1) { + this.line1 = line1; + } + + public String getLine2() { + return line2; + } + + public void setLine2(String line2) { + this.line2 = line2; + } + + public PostalCode getPostalCode() { + return postalCode; + } + + public void setPostalCode(PostalCode postalCode) { + this.postalCode = postalCode; + } + + + + public enum Classification { + HOME, + WORK, + MAIN, + OTHER + } + + @Embeddable + public static class PostalCode { + private int zipCode; + private int plus4; + + public int getZipCode() { + return zipCode; + } + + public void setZipCode(int zipCode) { + this.zipCode = zipCode; + } + + public int getPlus4() { + return plus4; + } + + public void setPlus4(int plus4) { + this.plus4 = plus4; + } + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/contacts/Contact.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/contacts/Contact.java new file mode 100644 index 000000000000..e2186efde091 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/contacts/Contact.java @@ -0,0 +1,145 @@ +/* + * 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.testing.orm.domain.contacts; + +import java.time.LocalDate; +import java.util.List; + +import javax.persistence.CollectionTable; +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Embeddable; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.OrderColumn; +import javax.persistence.SecondaryTable; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +/** + * @author Steve Ebersole + */ +@Entity +@Table( name = "contacts" ) +@SecondaryTable( name="contact_supp" ) +public class Contact { + private Integer id; + private Name name; + private Gender gender; + + private LocalDate birthDay; + + private List
      addresses; + private List phoneNumbers; + + public Contact() { + } + + public Contact(Integer id, Name name, Gender gender, LocalDate birthDay) { + this.id = id; + this.name = name; + this.gender = gender; + this.birthDay = birthDay; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Name getName() { + return name; + } + + public void setName(Name name) { + this.name = name; + } + + public Gender getGender() { + return gender; + } + + public void setGender(Gender gender) { + this.gender = gender; + } + + @Temporal( TemporalType.DATE ) + @Column( table = "contact_supp" ) + public LocalDate getBirthDay() { + return birthDay; + } + + public void setBirthDay(LocalDate birthDay) { + this.birthDay = birthDay; + } + + @ElementCollection + @CollectionTable( name = "contact_addresses" ) + // NOTE : because of the @OrderColumn `addresses` is a List, while `phoneNumbers` is + // a BAG which is a List with no persisted order + @OrderColumn + public List
      getAddresses() { + return addresses; + } + + public void setAddresses(List
      addresses) { + this.addresses = addresses; + } + + @ElementCollection + @CollectionTable( name = "contact_phones" ) + public List getPhoneNumbers() { + return phoneNumbers; + } + + public void setPhoneNumbers(List phoneNumbers) { + this.phoneNumbers = phoneNumbers; + } + + @Embeddable + public static class Name { + private String first; + private String last; + + public Name() { + } + + public Name(String first, String last) { + this.first = first; + this.last = last; + } + + @Column(name = "firstname") + public String getFirst() { + return first; + } + + public void setFirst(String first) { + this.first = first; + } + + @Column(name = "lastname") + public String getLast() { + return last; + } + + public void setLast(String last) { + this.last = last; + } + } + + public enum Gender { + MALE, + FEMALE, + OTHER + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/contacts/ContactsDomainModel.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/contacts/ContactsDomainModel.java new file mode 100644 index 000000000000..1c151a6503ba --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/contacts/ContactsDomainModel.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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.contacts; + +import org.hibernate.boot.MetadataSources; + +import org.hibernate.testing.orm.domain.AbstractDomainModelDescriptor; + +/** + * @author Steve Ebersole + */ +public class ContactsDomainModel extends AbstractDomainModelDescriptor { + public static ContactsDomainModel INSTANCE = new ContactsDomainModel(); + + public static void applyContactsModel(MetadataSources sources) { + INSTANCE.applyDomainModel( sources ); + } + + private ContactsDomainModel() { + super( + Address.class, + PhoneNumber.class, + Contact.class + ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/contacts/PhoneNumber.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/contacts/PhoneNumber.java new file mode 100644 index 000000000000..f0084ded5f49 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/contacts/PhoneNumber.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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.contacts; + +import javax.persistence.Embeddable; + +/** + * @author Steve Ebersole + */ +@Embeddable +public class PhoneNumber { + private int areaCode; + private int prefix; + private int lineNumber; + + private Classification classification; + + public PhoneNumber() { + } + + public PhoneNumber(int areaCode, int prefix, int lineNumber, Classification classification) { + this.areaCode = areaCode; + this.prefix = prefix; + this.lineNumber = lineNumber; + this.classification = classification; + } + + public int getAreaCode() { + return areaCode; + } + + public void setAreaCode(int areaCode) { + this.areaCode = areaCode; + } + + public int getPrefix() { + return prefix; + } + + public void setPrefix(int prefix) { + this.prefix = prefix; + } + + public int getLineNumber() { + return lineNumber; + } + + public void setLineNumber(int lineNumber) { + this.lineNumber = lineNumber; + } + + public Classification getClassification() { + return classification; + } + + public void setClassification(Classification classification) { + this.classification = classification; + } + + public enum Classification { + HOME, + WORK, + MOBILE, + MAIN, + FAX, + OTHER + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/BasicEntity.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/BasicEntity.java new file mode 100644 index 000000000000..474cd724ec23 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/BasicEntity.java @@ -0,0 +1,65 @@ +/* + * 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.testing.orm.domain.gambit; + +import java.util.Objects; + +import javax.persistence.Entity; +import javax.persistence.Id; + +/** + * @author Chris Cranford + */ +@Entity +public class BasicEntity { + @Id + private Integer id; + private String data; + + public BasicEntity() { + + } + + public BasicEntity(Integer id, String data) { + this.id = id; + this.data = data; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + BasicEntity that = (BasicEntity) o; + return Objects.equals( id, that.id ) && + Objects.equals( data, that.data ); + } + + @Override + public int hashCode() { + return Objects.hash( id, data ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/Component.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/Component.java new file mode 100644 index 000000000000..6dd38eb9d88d --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/Component.java @@ -0,0 +1,129 @@ +/* + * 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.testing.orm.domain.gambit; + +import javax.persistence.Embeddable; + +/** + * @author Steve Ebersole + */ +@Embeddable +@SuppressWarnings("unused") +public class Component { + + // alphabetical + private Integer basicInteger; + private Long basicLong; + private int basicPrimitiveInt; + private String basicString; + private Nested nested; + + @Embeddable + public static class Nested { + + // alphabetical + private String nestedValue; + private String secondNestedValue; + + public Nested() { + } + + public Nested(String nestedValue) { + this.nestedValue = nestedValue; + } + + public Nested(String nestedValue, String secondNestedValue) { + this.nestedValue = nestedValue; + this.secondNestedValue = secondNestedValue; + } + + public String getNestedValue() { + return nestedValue; + } + + public void setNestedValue(String nestedValue) { + this.nestedValue = nestedValue; + } + + public String getSecondNestedValue() { + return secondNestedValue; + } + + public void setSecondNestedValue(String secondNestedValue) { + this.secondNestedValue = secondNestedValue; + } + } + + public Component() { + } + + public Component( + String basicString, + Integer basicInteger, + Long basicLong, + int basicPrimitiveInt, + Nested nested) { + this.basicString = basicString; + this.basicInteger = basicInteger; + this.basicLong = basicLong; + this.basicPrimitiveInt = basicPrimitiveInt; + this.nested = nested; + } + + public Component( + Integer basicInteger, + Long basicLong, + int basicPrimitiveInt, + String basicString, + Nested nested) { + this.basicInteger = basicInteger; + this.basicLong = basicLong; + this.basicPrimitiveInt = basicPrimitiveInt; + this.basicString = basicString; + this.nested = nested; + } + + public String getBasicString() { + return basicString; + } + + public void setBasicString(String basicString) { + this.basicString = basicString; + } + + public Integer getBasicInteger() { + return basicInteger; + } + + public void setBasicInteger(Integer basicInteger) { + this.basicInteger = basicInteger; + } + + public Long getBasicLong() { + return basicLong; + } + + public void setBasicLong(Long basicLong) { + this.basicLong = basicLong; + } + + public int getBasicPrimitiveInt() { + return basicPrimitiveInt; + } + + public void setBasicPrimitiveInt(int basicPrimitiveInt) { + this.basicPrimitiveInt = basicPrimitiveInt; + } + + public Nested getNested() { + return nested; + } + + public void setNested(Nested nested) { + this.nested = nested; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EmbeddedIdEntity.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EmbeddedIdEntity.java new file mode 100644 index 000000000000..8f4c261a61e3 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EmbeddedIdEntity.java @@ -0,0 +1,97 @@ +/* + * 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.testing.orm.domain.gambit; + +import java.io.Serializable; +import java.util.Objects; + +import javax.persistence.Embeddable; +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; + +/** + * @author Chris Cranford + */ +@Entity +public class EmbeddedIdEntity { + @EmbeddedId + private EmbeddedIdEntityId id; + private String data; + + public EmbeddedIdEntityId getId() { + return id; + } + + public void setId(EmbeddedIdEntityId id) { + this.id = id; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + @Embeddable + public static class EmbeddedIdEntityId implements Serializable { + private Integer value1; + private String value2; + + EmbeddedIdEntityId() { + + } + + public EmbeddedIdEntityId(Integer value1, String value2) { + this.value1 = value1; + this.value2 = value2; + } + + public Integer getValue1() { + return value1; + } + + public void setValue1(Integer value1) { + this.value1 = value1; + } + + public String getValue2() { + return value2; + } + + public void setValue2(String value2) { + this.value2 = value2; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + EmbeddedIdEntityId that = (EmbeddedIdEntityId) o; + return Objects.equals( value1, that.value1 ) && + Objects.equals( value2, that.value2 ); + } + + @Override + public int hashCode() { + return Objects.hash( value1, value2 ); + } + + @Override + public String toString() { + return "EmbeddedIdEntityId{" + + "value1=" + value1 + + ", value2='" + value2 + '\'' + + '}'; + } + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfArrays.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfArrays.java new file mode 100644 index 000000000000..1be2720fadce --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfArrays.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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.gambit; + +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.OrderColumn; + +/** + * @author Koen Aers + */ +@SuppressWarnings("unused") +@Entity +public class EntityOfArrays { + + private Integer id; + private String name; + + private String[] arrayOfBasics; + + + public EntityOfArrays() { + } + + public EntityOfArrays(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; + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // arrayOfBasics + + @ElementCollection + @OrderColumn + public String[] getArrayOfBasics() { + return arrayOfBasics; + } + + public void setArrayOfBasics(String[] arrayOfBasics) { + this.arrayOfBasics = arrayOfBasics; + } + +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfBasics.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfBasics.java new file mode 100644 index 000000000000..f8b42c6c6272 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfBasics.java @@ -0,0 +1,336 @@ +/* + * 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.testing.orm.domain.gambit; + +import java.net.URL; +import java.sql.Clob; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.ZonedDateTime; +import java.util.Date; +import javax.persistence.AttributeConverter; +import javax.persistence.Column; +import javax.persistence.Convert; +import javax.persistence.Entity; +import javax.persistence.EntityResult; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.Id; +import javax.persistence.SqlResultSetMapping; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +import org.hibernate.annotations.Type; + +/** + * @author Steve Ebersole + */ +@SuppressWarnings( "unused" ) +@SqlResultSetMapping( + name = "entity-of-basics-implicit", + entities = @EntityResult( entityClass = EntityOfBasics.class ) +) +@Entity +public class EntityOfBasics { + + public enum Gender { + MALE, + FEMALE, + OTHER + } + + private Integer id; + private Boolean theBoolean = false; + private Boolean theNumericBoolean = false; + private Boolean theStringBoolean = false; + private String theString; + private Integer theInteger; + private int theInt; + private double theDouble; + private URL theUrl; + private Clob theClob; + private Date theDate; + private Date theTime; + private Date theTimestamp; + private Instant theInstant; + private Gender gender; + private Gender convertedGender; + private Gender ordinalGender; + private Duration theDuration; + + private LocalDateTime theLocalDateTime; + private LocalDate theLocalDate; + private LocalTime theLocalTime; + private ZonedDateTime theZonedDateTime; + private OffsetDateTime theOffsetDateTime; + + private MutableValue mutableValue; + + public EntityOfBasics() { + } + + public EntityOfBasics(Integer id) { + this.id = id; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getTheString() { + return theString; + } + + public void setTheString(String theString) { + this.theString = theString; + } + + public Integer getTheInteger() { + return theInteger; + } + + public void setTheInteger(Integer theInteger) { + this.theInteger = theInteger; + } + + public int getTheInt() { + return theInt; + } + + public void setTheInt(int theInt) { + this.theInt = theInt; + } + + public double getTheDouble() { + return theDouble; + } + + public void setTheDouble(double theDouble) { + this.theDouble = theDouble; + } + + public URL getTheUrl() { + return theUrl; + } + + public void setTheUrl(URL theUrl) { + this.theUrl = theUrl; + } + + public Clob getTheClob() { + return theClob; + } + + public void setTheClob(Clob theClob) { + this.theClob = theClob; + } + + @Enumerated( EnumType.STRING ) + public Gender getGender() { + return gender; + } + + public void setGender(Gender gender) { + this.gender = gender; + } + + @Convert( converter = GenderConverter.class ) + @Column(name = "converted_gender", length = 1) + public Gender getConvertedGender() { + return convertedGender; + } + + public void setConvertedGender(Gender convertedGender) { + this.convertedGender = convertedGender; + } + + @Column(name = "ordinal_gender") + public Gender getOrdinalGender() { + return ordinalGender; + } + + public void setOrdinalGender(Gender ordinalGender) { + this.ordinalGender = ordinalGender; + } + + @Temporal( TemporalType.DATE ) + public Date getTheDate() { + return theDate; + } + + public void setTheDate(Date theDate) { + this.theDate = theDate; + } + + @Temporal( TemporalType.TIME ) + public Date getTheTime() { + return theTime; + } + + public void setTheTime(Date theTime) { + this.theTime = theTime; + } + + @Temporal( TemporalType.TIMESTAMP ) + public Date getTheTimestamp() { + return theTimestamp; + } + + public void setTheTimestamp(Date theTimestamp) { + this.theTimestamp = theTimestamp; + } + + @Temporal( TemporalType.TIMESTAMP ) + public Instant getTheInstant() { + return theInstant; + } + + public void setTheInstant(Instant theInstant) { + this.theInstant = theInstant; + } + + public LocalDateTime getTheLocalDateTime() { + return theLocalDateTime; + } + + public void setTheLocalDateTime(LocalDateTime theLocalDateTime) { + this.theLocalDateTime = theLocalDateTime; + } + + public LocalDate getTheLocalDate() { + return theLocalDate; + } + + public void setTheLocalDate(LocalDate theLocalDate) { + this.theLocalDate = theLocalDate; + } + + public LocalTime getTheLocalTime() { + return theLocalTime; + } + + public void setTheLocalTime(LocalTime theLocalTime) { + this.theLocalTime = theLocalTime; + } + + public OffsetDateTime getTheOffsetDateTime() { + return theOffsetDateTime; + } + + public void setTheOffsetDateTime(OffsetDateTime theOffsetDateTime) { + this.theOffsetDateTime = theOffsetDateTime; + } + + public ZonedDateTime getTheZonedDateTime() { + return theZonedDateTime; + } + + public void setTheZonedDateTime(ZonedDateTime theZonedDateTime) { + this.theZonedDateTime = theZonedDateTime; + } + + public Duration getTheDuration() { + return theDuration; + } + + public void setTheDuration(Duration theDuration) { + this.theDuration = theDuration; + } + + public Boolean isTheBoolean() { + return theBoolean; + } + + public void setTheBoolean(Boolean theBoolean) { + this.theBoolean = theBoolean; + } + + @Type( type = "numeric_boolean" ) + public Boolean isTheNumericBoolean() { + return theNumericBoolean; + } + + public void setTheNumericBoolean(Boolean theNumericBoolean) { + this.theNumericBoolean = theNumericBoolean; + } + + @Type( type = "true_false" ) + public Boolean isTheStringBoolean() { + return theStringBoolean; + } + + public void setTheStringBoolean(Boolean theStringBoolean) { + this.theStringBoolean = theStringBoolean; + } + + @Convert( converter = MutableValueConverter.class ) + public MutableValue getMutableValue() { + return mutableValue; + } + + public void setMutableValue(MutableValue mutableValue) { + this.mutableValue = mutableValue; + } + + public static class MutableValueConverter implements AttributeConverter { + @Override + public String convertToDatabaseColumn(MutableValue attribute) { + return attribute == null ? null : attribute.getState(); + } + + @Override + public MutableValue convertToEntityAttribute(String dbData) { + return dbData == null ? null : new MutableValue( dbData ); + } + } + + public static class GenderConverter implements AttributeConverter { + @Override + public Character convertToDatabaseColumn(Gender attribute) { + if ( attribute == null ) { + return null; + } + + if ( attribute == Gender.OTHER ) { + return 'O'; + } + + if ( attribute == Gender.MALE ) { + return 'M'; + } + + return 'F'; + } + + @Override + public Gender convertToEntityAttribute(Character dbData) { + if ( dbData == null ) { + return null; + } + + if ( 'O' == dbData ) { + return Gender.OTHER; + } + + if ( 'M' == dbData ) { + return Gender.MALE; + } + + return Gender.FEMALE; + } + } +} + diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfComposites.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfComposites.java new file mode 100644 index 000000000000..03eb5221d90f --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfComposites.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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.gambit; + +import javax.persistence.Embedded; +import javax.persistence.Entity; +import javax.persistence.Id; + +/** + * @author Steve Ebersole + */ +@SuppressWarnings("unused") +@Entity +public class EntityOfComposites { + private Integer id; + private String name; + private Component component; + + public EntityOfComposites() { + } + + public EntityOfComposites(Integer id) { + this.id = id; + } + + public EntityOfComposites(Integer id, Component component) { + this.id = id; + this.component = component; + } + + public EntityOfComposites(Integer id, String name, Component component) { + this.id = id; + this.name = name; + this.component = component; + } + + @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; + } + + @Embedded + public Component getComponent() { + return component; + } + + public void setComponent(Component component) { + this.component = component; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfDynamicComponent.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfDynamicComponent.java new file mode 100644 index 000000000000..6aa5038c705b --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfDynamicComponent.java @@ -0,0 +1,62 @@ +/* + * 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.testing.orm.domain.gambit; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author Chris Cranford + */ +public class EntityOfDynamicComponent { + private Long id; + private String note; + private Map values = new HashMap<>(); + private Map valuesWithProperties = new HashMap<>(); + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getNote() { + return note; + } + + public void setNote(String note) { + this.note = note; + } + + public Map getValues() { + return values; + } + + public void setValues(Map values) { + this.values = values; + } + + public Map getValuesWithProperties() { + return valuesWithProperties; + } + + public void setValuesWithProperties(Map valuesWithProperties) { + this.valuesWithProperties = valuesWithProperties; + } + + @Override + public String toString() { + return "EntityOfDynamicComponent{" + + "id=" + id + + ", note='" + note + '\'' + + ", values=" + values + + ", valuesWithProperties=" + valuesWithProperties + + '}'; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfLists.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfLists.java new file mode 100644 index 000000000000..9950b9770fa6 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfLists.java @@ -0,0 +1,219 @@ +/* + * 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.testing.orm.domain.gambit; + +import java.util.ArrayList; +import java.util.List; + +import javax.persistence.CollectionTable; +import javax.persistence.Convert; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.Id; +import javax.persistence.ManyToMany; +import javax.persistence.OneToMany; +import javax.persistence.OrderColumn; + +/** + * @author Steve Ebersole + */ +@SuppressWarnings("unused") +@Entity +public class EntityOfLists { + private Integer id; + private String name; + + private List listOfBasics; + private List listOfNumbers; + + private List listOfConvertedEnums; + private List listOfEnums; + + private List listOfComponents; + + private List listOfOneToMany; + private List listOfManyToMany; + + public EntityOfLists() { + } + + public EntityOfLists(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; + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // listOfBasics + + @ElementCollection + @OrderColumn + @CollectionTable(name = "EntityOfLists_basic") + public List getListOfBasics() { + return listOfBasics; + } + + public void setListOfBasics(List listOfBasics) { + this.listOfBasics = listOfBasics; + } + + @ElementCollection + @OrderColumn(name="num_indx") + @CollectionTable(name = "EntityOfLists_numbers") + public List getListOfNumbers() { + return listOfNumbers; + } + + public void setListOfNumbers(List listOfNumbers) { + this.listOfNumbers = listOfNumbers; + } + + public void addBasic(String basic) { + if ( listOfBasics == null ) { + listOfBasics = new ArrayList<>(); + } + listOfBasics.add( basic ); + } + + public void addNumber(double number) { + if ( listOfNumbers == null ) { + listOfNumbers = new ArrayList<>(); + } + listOfNumbers.add( number ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // listOfConvertedEnums + + @ElementCollection + @OrderColumn + @Convert(converter = EnumValueConverter.class) + @CollectionTable(name = "EntityOfLists_enum1") + public List getListOfConvertedEnums() { + return listOfConvertedEnums; + } + + public void setListOfConvertedEnums(List listOfConvertedEnums) { + this.listOfConvertedEnums = listOfConvertedEnums; + } + + public void addConvertedEnum(EnumValue value) { + if ( listOfConvertedEnums == null ) { + listOfConvertedEnums = new ArrayList<>(); + } + listOfConvertedEnums.add( value ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // listOfEnums + + @ElementCollection + @Enumerated(EnumType.STRING) + @OrderColumn + @CollectionTable(name = "EntityOfLists_enum2") + public List getListOfEnums() { + return listOfEnums; + } + + public void setListOfEnums(List listOfEnums) { + this.listOfEnums = listOfEnums; + } + + public void addEnum(EnumValue value) { + if ( listOfEnums == null ) { + listOfEnums = new ArrayList<>(); + } + listOfEnums.add( value ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // listOfComponents + + @ElementCollection + @OrderColumn + @CollectionTable(name = "EntityOfLists_comp") + public List getListOfComponents() { + return listOfComponents; + } + + public void setListOfComponents(List listOfComponents) { + this.listOfComponents = listOfComponents; + } + + public void addComponent(SimpleComponent value) { + if ( listOfComponents == null ) { + listOfComponents = new ArrayList<>(); + } + listOfComponents.add( value ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // listOfOneToMany + + @OneToMany + @OrderColumn + @CollectionTable(name = "EntityOfLists_o2m") + public List getListOfOneToMany() { + return listOfOneToMany; + } + + public void setListOfOneToMany(List listOfOneToMany) { + this.listOfOneToMany = listOfOneToMany; + } + + public void addOneToMany(SimpleEntity value) { + if ( listOfOneToMany == null ) { + listOfOneToMany = new ArrayList<>(); + } + listOfOneToMany.add( value ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // listOfManyToMany + + @ManyToMany + @OrderColumn + @CollectionTable(name = "EntityOfLists_m2m") + public List getListOfManyToMany() { + return listOfManyToMany; + } + + public void setListOfManyToMany(List listOfManyToMany) { + this.listOfManyToMany = listOfManyToMany; + } + + public void addManyToMany(SimpleEntity value) { + if ( listOfManyToMany == null ) { + listOfManyToMany = new ArrayList<>(); + } + listOfManyToMany.add( value ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfMaps.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfMaps.java new file mode 100644 index 000000000000..dfceb4d3ffe0 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfMaps.java @@ -0,0 +1,453 @@ +/* + * 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.testing.orm.domain.gambit; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; + +import org.hibernate.annotations.OrderBy; +import org.hibernate.annotations.SortComparator; +import org.hibernate.annotations.SortNatural; + +import javax.persistence.CollectionTable; +import javax.persistence.Column; +import javax.persistence.Convert; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; +import javax.persistence.MapKeyColumn; +import javax.persistence.MapKeyEnumerated; +import javax.persistence.MapKeyJoinColumn; +import javax.persistence.OneToMany; + +/** + * @author Steve Ebersole + * @author Fabio Massimo Ercoli + */ +@SuppressWarnings("unused") +@Entity +public class EntityOfMaps { + private Integer id; + private String name; + + private Map basicByBasic; + private Map numberByNumber; + + private SortedMap sortedBasicByBasic; + private SortedMap sortedBasicByBasicWithComparator; + private SortedMap sortedBasicByBasicWithSortNaturalByDefault; + + private Map basicByEnum; + private Map basicByConvertedEnum; + + private Map componentByBasic; + private Map basicByComponent; + + private Map oneToManyByBasic; + private Map basicByOneToMany; + + private Map manyToManyByBasic; + private Map componentByBasicOrdered; + + private SortedMap sortedManyToManyByBasic; + private SortedMap sortedManyToManyByBasicWithComparator; + private SortedMap sortedManyToManyByBasicWithSortNaturalByDefault; + + public EntityOfMaps() { + } + + public EntityOfMaps(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; + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // basicByBasic + + @ElementCollection + @CollectionTable(name = "EntityOfMaps_basic_basic1") + @MapKeyColumn(name = "basic_key") + @Column(name = "basic_val") + public Map getBasicByBasic() { + return basicByBasic; + } + + public void setBasicByBasic(Map basicByBasic) { + this.basicByBasic = basicByBasic; + } + + public void addBasicByBasic(String key, String val) { + if ( basicByBasic == null ) { + basicByBasic = new HashMap<>(); + } + basicByBasic.put( key, val ); + } + + @ElementCollection + @CollectionTable(name = "EntityOfMaps_number_number1") + @MapKeyColumn(name = "number_key") + @Column(name = "number_val") + public Map getNumberByNumber() { + return numberByNumber; + } + + public void setNumberByNumber(Map numberByNumber) { + this.numberByNumber = numberByNumber; + } + + public void addNumberByNumber(int key, double val) { + if ( numberByNumber == null ) { + numberByNumber = new HashMap<>(); + } + numberByNumber.put( key, val ); + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // sortedBasicByBasic + + @ElementCollection + @SortNatural + @CollectionTable(name = "EntityOfMaps_basic_basic2") + @MapKeyColumn(name = "basic_key") + @Column(name = "basic_val") + public SortedMap getSortedBasicByBasic() { + return sortedBasicByBasic; + } + + public void setSortedBasicByBasic(SortedMap sortedBasicByBasic) { + this.sortedBasicByBasic = sortedBasicByBasic; + } + + public void addSortedBasicByBasic(String key, String val) { + if ( sortedBasicByBasic == null ) { + sortedBasicByBasic = new TreeMap<>(); + } + sortedBasicByBasic.put( key, val ); + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // sortedBasicByBasicWithComparator + + @ElementCollection + @SortComparator( SimpleBasicSortComparator.class ) + @CollectionTable(name = "EntityOfMaps_basic_basic3") + @MapKeyColumn(name = "basic_key") + @Column(name = "basic_val") + public SortedMap getSortedBasicByBasicWithComparator() { + return sortedBasicByBasicWithComparator; + } + + public void setSortedBasicByBasicWithComparator(SortedMap sortedBasicByBasicWithComparator) { + this.sortedBasicByBasicWithComparator = sortedBasicByBasicWithComparator; + } + + public void addSortedBasicByBasicWithComparator(String key, String val) { + if ( sortedBasicByBasicWithComparator == null ) { + sortedBasicByBasicWithComparator = new TreeMap<>(); + } + sortedBasicByBasicWithComparator.put( key, val ); + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // sortedBasicByBasicWithSortNaturalByDefault + + @ElementCollection + @CollectionTable(name = "EntityOfMaps_basic_basic4") + @MapKeyColumn(name = "basic_key") + @Column(name = "basic_val") + public SortedMap getSortedBasicByBasicWithSortNaturalByDefault() { + return sortedBasicByBasicWithSortNaturalByDefault; + } + + public void setSortedBasicByBasicWithSortNaturalByDefault(SortedMap sortedBasicByBasicWithSortNaturalByDefault) { + this.sortedBasicByBasicWithSortNaturalByDefault = sortedBasicByBasicWithSortNaturalByDefault; + } + + public void addSortedBasicByBasicWithSortNaturalByDefault(String key, String val) { + if ( sortedBasicByBasicWithSortNaturalByDefault == null ) { + sortedBasicByBasicWithSortNaturalByDefault = new TreeMap<>(); + } + sortedBasicByBasicWithSortNaturalByDefault.put( key, val ); + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // basicByEnum + + @ElementCollection + @MapKeyEnumerated + @CollectionTable(name = "EntityOfMaps_basic_enum1") + @MapKeyColumn(name = "enum_key") + @Column(name = "basic_val") + public Map getBasicByEnum() { + return basicByEnum; + } + + public void setBasicByEnum(Map basicByEnum) { + this.basicByEnum = basicByEnum; + } + + public void addBasicByEnum(EnumValue key, String val) { + if ( basicByEnum == null ) { + basicByEnum = new HashMap<>(); + } + basicByEnum.put( key, val ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // basicByConvertedEnum + + @ElementCollection + @Convert(attributeName = "key", converter = EnumValueConverter.class) + @CollectionTable(name = "EntityOfMaps_basic_enum2") + @MapKeyColumn(name = "enum_key") + @Column(name = "basic_val") + public Map getBasicByConvertedEnum() { + return basicByConvertedEnum; + } + + public void setBasicByConvertedEnum(Map basicByConvertedEnum) { + this.basicByConvertedEnum = basicByConvertedEnum; + } + + public void addBasicByConvertedEnum(EnumValue key, String value) { + if ( basicByConvertedEnum == null ) { + basicByConvertedEnum = new HashMap<>(); + } + basicByConvertedEnum.put( key, value ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // componentByBasic + + @ElementCollection + @CollectionTable(name = "EntityOfMaps_comp_basic1") + @MapKeyColumn(name = "basic_key") + public Map getComponentByBasic() { + return componentByBasic; + } + + public void setComponentByBasic(Map componentByBasic) { + this.componentByBasic = componentByBasic; + } + + public void addComponentByBasic(String key, SimpleComponent value) { + if ( componentByBasic == null ) { + componentByBasic = new HashMap<>(); + } + componentByBasic.put( key, value ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // basicByComponent + + @ElementCollection + @CollectionTable(name = "EntityOfMaps_basic_comp") + @Column(name = "basic_val") + public Map getBasicByComponent() { + return basicByComponent; + } + + public void setBasicByComponent(Map basicByComponent) { + this.basicByComponent = basicByComponent; + } + + public void addBasicByComponent(SimpleComponent key, String value) { + if ( basicByComponent == null ) { + basicByComponent = new HashMap<>(); + } + basicByComponent.put( key, value ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // oneToManyByBasic + + @OneToMany + @JoinColumn + @MapKeyColumn(name = "basic_key") + @JoinTable(name = "EntityOfMaps_o2m_basic", + joinColumns = @JoinColumn(name = "EntityOfMaps_o2m_basic_id1"), + inverseJoinColumns = @JoinColumn(name = "EntityOfMaps_o2m_basic_id2")) + public Map getOneToManyByBasic() { + return oneToManyByBasic; + } + + public void setOneToManyByBasic(Map oneToManyByBasic) { + this.oneToManyByBasic = oneToManyByBasic; + } + + public void addOneToManyByBasic(String key, SimpleEntity value) { + if ( oneToManyByBasic == null ) { + oneToManyByBasic = new HashMap<>(); + } + oneToManyByBasic.put( key, value ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // basicByOneToMany + + @ElementCollection + @CollectionTable(name = "EntityOfMaps_basic_o2m") + @Column(name = "basic_val") + @MapKeyJoinColumn(name = "EntityOfMaps_basic_o2m_key") + public Map getBasicByOneToMany() { + return basicByOneToMany; + } + + public void setBasicByOneToMany(Map basicByOneToMany) { + this.basicByOneToMany = basicByOneToMany; + } + + public void addOneToManyByBasic(SimpleEntity key, String val) { + if ( basicByOneToMany == null ) { + basicByOneToMany = new HashMap<>(); + } + basicByOneToMany.put( key, val ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // manyToManyByBasic + + @ManyToMany + @MapKeyColumn(name = "basic_key") + @JoinTable(name = "EntityOfMaps_m2m_basic1", + joinColumns = @JoinColumn(name = "EntityOfMaps_m2m_basic1_id1"), + inverseJoinColumns = @JoinColumn(name = "EntityOfMaps_m2m_basic1_id2")) + public Map getManyToManyByBasic() { + return manyToManyByBasic; + } + + public void setManyToManyByBasic(Map manyToManyByBasic) { + this.manyToManyByBasic = manyToManyByBasic; + } + + public void addManyToManyByComponent(String key, SimpleEntity value) { + if ( manyToManyByBasic == null ) { + manyToManyByBasic = new HashMap<>(); + } + manyToManyByBasic.put( key, value ); + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // componentByBasicOrdered + + // NOTE : effectively the same as a natural-sorted map in terms of reading + + @ElementCollection + @MapKeyColumn( name = "ordered_component_key") + @OrderBy( clause = "ordered_component_key, ordered_component_key" ) + @CollectionTable(name = "EntityOfMaps_comp_basic2") + public Map getComponentByBasicOrdered() { + return componentByBasicOrdered; + } + + public void setComponentByBasicOrdered(Map componentByBasicOrdered) { + this.componentByBasicOrdered = componentByBasicOrdered; + } + + public void addComponentByBasicOrdered(String key, SimpleComponent value) { + if ( componentByBasicOrdered == null ) { + componentByBasicOrdered = new LinkedHashMap<>(); + } + componentByBasicOrdered.put( key, value ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // sortedManyToManyByBasic + + @ManyToMany + @SortNatural + @MapKeyColumn(name = "basic_key") + @JoinTable(name = "EntityOfMaps_m2m_basic2", + joinColumns = @JoinColumn(name = "EntityOfMaps_m2m_basic2_id1"), + inverseJoinColumns = @JoinColumn(name = "EntityOfMaps_m2m_basic2_id2")) + public SortedMap getSortedManyToManyByBasic() { + return sortedManyToManyByBasic; + } + + public void setSortedManyToManyByBasic(SortedMap sortedManyToManyByBasic) { + this.sortedManyToManyByBasic = sortedManyToManyByBasic; + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // sortedManyToManyByBasicWithComparator + + @ManyToMany + @SortComparator( SimpleBasicSortComparator.class ) + @JoinTable(name = "EntityOfMaps_m2m_basic3", + joinColumns = @JoinColumn(name = "EntityOfMaps_m2m_basic3_id1"), + inverseJoinColumns = @JoinColumn(name = "EntityOfMaps_m2m_basic3_id2")) + @MapKeyColumn(name = "basic_key") + public SortedMap getSortedManyToManyByBasicWithComparator() { + return sortedManyToManyByBasicWithComparator; + } + + public void setSortedManyToManyByBasicWithComparator(SortedMap sortedManyToManyByBasicWithComparator) { + this.sortedManyToManyByBasicWithComparator = sortedManyToManyByBasicWithComparator; + } + + public void addSortedManyToManyByBasicWithComparator(String key, SimpleEntity value) { + if ( sortedManyToManyByBasicWithComparator == null ) { + sortedManyToManyByBasicWithComparator = new TreeMap<>(); + } + sortedManyToManyByBasicWithComparator.put( key, value ); + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // sortedManyToManyByBasicWithSortNaturalByDefault + + @ManyToMany + @MapKeyColumn(name = "basic_key") + @JoinTable(name = "EntityOfMaps_m2m_basic4", + joinColumns = @JoinColumn(name = "EntityOfMaps_m2m_basic4_id1"), + inverseJoinColumns = @JoinColumn(name = "EntityOfMaps_m2m_basic4_id2")) + public SortedMap getSortedManyToManyByBasicWithSortNaturalByDefault() { + return sortedManyToManyByBasicWithSortNaturalByDefault; + } + + public void setSortedManyToManyByBasicWithSortNaturalByDefault(SortedMap sortedManyToManyByBasicWithSortNaturalByDefault) { + this.sortedManyToManyByBasicWithSortNaturalByDefault = sortedManyToManyByBasicWithSortNaturalByDefault; + } + + public void addSortedManyToManyByBasicWithSortNaturalByDefault(String key, SimpleEntity value) { + if ( sortedManyToManyByBasicWithSortNaturalByDefault == null ) { + sortedManyToManyByBasicWithSortNaturalByDefault = new TreeMap<>(); + } + sortedManyToManyByBasicWithSortNaturalByDefault.put( key, value ); + } + +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfSets.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfSets.java new file mode 100644 index 000000000000..27e81a701e05 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfSets.java @@ -0,0 +1,345 @@ +/* + * 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.testing.orm.domain.gambit; + +import java.util.HashSet; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + +import org.hibernate.annotations.LazyCollection; +import org.hibernate.annotations.LazyCollectionOption; +import org.hibernate.annotations.SortComparator; +import org.hibernate.annotations.SortNatural; + +import javax.persistence.CollectionTable; +import javax.persistence.Column; +import javax.persistence.Convert; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.Id; +import javax.persistence.ManyToMany; +import javax.persistence.OneToMany; +import javax.persistence.OrderBy; +import javax.persistence.Table; + +/** + * @author Steve Ebersole + */ +@SuppressWarnings("unused") +@Entity +@Table(name = "entity_containing_sets") +public class EntityOfSets { + @Id + private Integer id; + private String name; + + @ElementCollection() + @CollectionTable( name = "EntityOfSet_basic1") + @Column(name = "basic_val") + private Set setOfBasics; + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Sorted + + @ElementCollection() + @CollectionTable( name = "EntityOfSet_basic2") + @Column(name = "basic_val") + private SortedSet sortedSetOfBasicsWithSortNaturalByDefault; + + @ElementCollection() + @CollectionTable( name = "EntityOfSet_basic3") + @SortNatural + @Column(name = "basic_val") + private SortedSet sortedSetOfBasics; + + @ElementCollection() + @CollectionTable( name = "EntityOfSet_basic4") + @SortComparator( SimpleBasicSortComparator.class ) + @Column(name = "basic_val") + private SortedSet sortedSetOfBasicsWithComparator; + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Ordered + + @ElementCollection() + @CollectionTable( name = "EntityOfSet_basic5") + @OrderBy( "" ) + @Column(name = "basic_val") + private Set orderedSetOfBasics; + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Enum elements + + @ElementCollection + @Enumerated(EnumType.STRING) + @CollectionTable(name = "EntityOfSet_enum1") + @Column(name = "enum_val") + private Set setOfEnums; + + @ElementCollection + @Convert(converter = EnumValueConverter.class) + @CollectionTable(name = "EntityOfSet_enum2") + @Column(name = "enum_val") + private Set setOfConvertedEnums; + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Embeddables + + @ElementCollection + @CollectionTable( name = "EntityOfSet_comp1") + private Set setOfComponents; + + @ElementCollection + @LazyCollection( LazyCollectionOption.EXTRA ) + @CollectionTable( name = "EntityOfSet_comp2") + private Set extraLazySetOfComponents; + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Entity associations + + @OneToMany + @CollectionTable( name = "EntityOfSet_o2m") + private Set setOfOneToMany; + + @ManyToMany + @CollectionTable( name = "EntityOfSet_m2m") + private Set setOfManyToMany; + + + public EntityOfSets() { + } + + public EntityOfSets(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; + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // setOfBasics + + public Set getSetOfBasics() { + return setOfBasics; + } + + public void setSetOfBasics(Set setOfBasics) { + this.setOfBasics = setOfBasics; + } + + public void addBasic(String value) { + if ( setOfBasics == null ) { + setOfBasics = new HashSet<>(); + } + setOfBasics.add( value ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // orderedSetOfBasics + + public Set getOrderedSetOfBasics() { + return orderedSetOfBasics; + } + + public void setOrderedSetOfBasics(Set orderedSetOfBasics) { + this.orderedSetOfBasics = orderedSetOfBasics; + } + + public void addOrderedBasic(String value) { + if ( orderedSetOfBasics == null ) { + orderedSetOfBasics = new TreeSet<>(); + } + orderedSetOfBasics.add( value ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // sortedSetOfBasics + + public SortedSet getSortedSetOfBasics() { + return sortedSetOfBasics; + } + + public void setSortedSetOfBasics(SortedSet sortedSetOfBasics) { + this.sortedSetOfBasics = sortedSetOfBasics; + } + + public void addSortedBasic(String value) { + if ( sortedSetOfBasics == null ) { + sortedSetOfBasics = new TreeSet<>(); + } + sortedSetOfBasics.add( value ); + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // sortedSetOfBasicsWithComparator + + public SortedSet getSortedSetOfBasicsWithComparator() { + return sortedSetOfBasicsWithComparator; + } + + public void setSortedSetOfBasicsWithComparator(SortedSet sortedSetOfBasicsWithComparator) { + this.sortedSetOfBasicsWithComparator = sortedSetOfBasicsWithComparator; + } + + public void addSortedBasicWithComparator(String value) { + if ( sortedSetOfBasicsWithComparator == null ) { + sortedSetOfBasicsWithComparator = new TreeSet<>(); + } + sortedSetOfBasicsWithComparator.add( value ); + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // sortedSetOfBasicsWithSortNaturalByDefault + + public SortedSet getSortedSetOfBasicsWithSortNaturalByDefault() { + return sortedSetOfBasicsWithSortNaturalByDefault; + } + + public void setSortedSetOfBasicsWithSortNaturalByDefault(SortedSet sortedSetOfBasicsWithSortNaturalByDefault) { + this.sortedSetOfBasicsWithSortNaturalByDefault = sortedSetOfBasicsWithSortNaturalByDefault; + } + + public void addSortedBasicWithSortNaturalByDefault(String value) { + if ( sortedSetOfBasicsWithSortNaturalByDefault == null ) { + sortedSetOfBasicsWithSortNaturalByDefault = new TreeSet<>(); + } + sortedSetOfBasicsWithSortNaturalByDefault.add( value ); + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // setOfConvertedEnums + + public Set getSetOfConvertedEnums() { + return setOfConvertedEnums; + } + + public void setSetOfConvertedEnums(Set setOfConvertedEnums) { + this.setOfConvertedEnums = setOfConvertedEnums; + } + + public void addConvertedEnum(EnumValue value) { + if ( setOfConvertedEnums == null ) { + setOfConvertedEnums = new HashSet<>(); + } + setOfConvertedEnums.add( value ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // setOfEnums + + public Set getSetOfEnums() { + return setOfEnums; + } + + public void setSetOfEnums(Set setOfEnums) { + this.setOfEnums = setOfEnums; + } + + public void addEnum(EnumValue value) { + if ( setOfEnums == null ) { + setOfEnums = new HashSet<>(); + } + setOfEnums.add( value ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // setOfComponents + + public Set getSetOfComponents() { + return setOfComponents; + } + + public void setSetOfComponents(Set setOfComponents) { + this.setOfComponents = setOfComponents; + } + + public void addComponent(SimpleComponent value) { + if ( setOfComponents == null ) { + setOfComponents = new HashSet<>(); + } + setOfComponents.add( value ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // setOfExtraLazyComponents + + public Set getExtraLazySetOfComponents() { + return extraLazySetOfComponents; + } + + public void setExtraLazySetOfComponents(Set extraLazySetOfComponents) { + this.extraLazySetOfComponents = extraLazySetOfComponents; + } + + public void addExtraLazyComponent(SimpleComponent value) { + if ( extraLazySetOfComponents == null ) { + extraLazySetOfComponents = new HashSet<>(); + } + extraLazySetOfComponents.add( value ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // setOfOneToMany + + public Set getSetOfOneToMany() { + return setOfOneToMany; + } + + public void setSetOfOneToMany(Set setOfOneToMany) { + this.setOfOneToMany = setOfOneToMany; + } + + public void addOneToMany(SimpleEntity value) { + if ( setOfOneToMany == null ) { + setOfOneToMany = new HashSet<>(); + } + setOfOneToMany.add( value ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // setOfManyToMany + + public Set getSetOfManyToMany() { + return setOfManyToMany; + } + + public void setSetOfManyToMany(Set setOfManyToMany) { + this.setOfManyToMany = setOfManyToMany; + } + + public void addManyToMany(SimpleEntity value) { + if ( setOfManyToMany == null ) { + setOfManyToMany = new HashSet<>(); + } + setOfManyToMany.add( value ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithAggregateId.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithAggregateId.java new file mode 100644 index 000000000000..43688265c062 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithAggregateId.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.testing.orm.domain.gambit; + +import java.io.Serializable; + +import javax.persistence.Embeddable; +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; + +/** + * @author Steve Ebersole + */ +@Entity +public class EntityWithAggregateId { + private Key key; + private String data; + + public EntityWithAggregateId() { + } + + public EntityWithAggregateId(Key key, String data) { + this.key = key; + this.data = data; + } + + @EmbeddedId + public Key getKey() { + return key; + } + + public void setKey(Key key) { + this.key = key; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + + @Embeddable + public static class Key implements Serializable { + private String value1; + private String value2; + + public Key() { + } + + public Key(String value1, String value2) { + this.value1 = value1; + this.value2 = value2; + } + + public String getValue1() { + return value1; + } + + public void setValue1(String value1) { + this.value1 = value1; + } + + public String getValue2() { + return value2; + } + + public void setValue2(String value2) { + this.value2 = value2; + } + } + +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithLazyManyToOneSelfReference.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithLazyManyToOneSelfReference.java new file mode 100644 index 000000000000..b0b7de5ed9c9 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithLazyManyToOneSelfReference.java @@ -0,0 +1,95 @@ +/* + * 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.testing.orm.domain.gambit; + +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +/** + * @author Andrea Boriero + */ +@Entity +@Table(name = "entity_lm2o_selfref") +public class EntityWithLazyManyToOneSelfReference { + private Integer id; + + // alphabetical + private String name; + private EntityWithLazyManyToOneSelfReference other; + private Integer someInteger; + + EntityWithLazyManyToOneSelfReference() { + } + + public EntityWithLazyManyToOneSelfReference(Integer id, String name, Integer someInteger) { + this.id = id; + this.name = name; + this.someInteger = someInteger; + } + + public EntityWithLazyManyToOneSelfReference( + Integer id, + String name, + Integer someInteger, + EntityWithLazyManyToOneSelfReference other) { + this.id = id; + this.name = name; + this.someInteger = someInteger; + this.other = other; + } + + public EntityWithLazyManyToOneSelfReference( + Integer id, + String name, + EntityWithLazyManyToOneSelfReference other, + Integer someInteger) { + this.id = id; + this.name = name; + this.other = other; + this.someInteger = someInteger; + } + + @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; + } + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn + public EntityWithLazyManyToOneSelfReference getOther() { + return other; + } + + public void setOther(EntityWithLazyManyToOneSelfReference other) { + this.other = other; + } + + public Integer getSomeInteger() { + return someInteger; + } + + public void setSomeInteger(Integer someInteger) { + this.someInteger = someInteger; + } +} + diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithLazyOneToOne.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithLazyOneToOne.java new file mode 100644 index 000000000000..cd5464ebc2e5 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithLazyOneToOne.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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.gambit; + +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.OneToOne; + +/** + * @author Andrea Boriero + */ +@Entity +public class EntityWithLazyOneToOne { + private Integer id; + + // alphabetical + private String name; + private SimpleEntity other; + private Integer someInteger; + + public EntityWithLazyOneToOne() { + } + + public EntityWithLazyOneToOne(Integer id, String name, Integer someInteger) { + this.id = id; + this.name = name; + this.someInteger = someInteger; + } + + @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) + public SimpleEntity getOther() { + return other; + } + + public void setOther(SimpleEntity other) { + this.other = other; + } + + public Integer getSomeInteger() { + return someInteger; + } + + public void setSomeInteger(Integer someInteger) { + this.someInteger = someInteger; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithManyToOneJoinTable.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithManyToOneJoinTable.java new file mode 100644 index 000000000000..69d8f54b0ad9 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithManyToOneJoinTable.java @@ -0,0 +1,89 @@ +/* + * 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.testing.orm.domain.gambit; + +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToOne; + +/** + * @author Andrea Boriero + */ +@Entity +public class EntityWithManyToOneJoinTable { + private Integer id; + + // alphabetical + private String name; + private SimpleEntity other; + private Integer someInteger; + private BasicEntity lazyOther; + + public EntityWithManyToOneJoinTable() { + } + + public EntityWithManyToOneJoinTable(Integer id, String name, Integer someInteger) { + this.id = id; + this.name = name; + this.someInteger = someInteger; + } + + @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; + } + + @ManyToOne + @JoinTable(name = "ENTITY_OTHER", + joinColumns = { + @JoinColumn( name = "LHS_ID") + }, + inverseJoinColumns = { + @JoinColumn(name="RHS_ID") + } + ) + public SimpleEntity getOther() { + return other; + } + + public void setOther(SimpleEntity other) { + this.other = other; + } + + @ManyToOne(fetch = FetchType.LAZY) + @JoinTable(name = "ENTITY_ANOTHER") + public BasicEntity getLazyOther() { + return lazyOther; + } + + public void setLazyOther(BasicEntity lazyOther) { + this.lazyOther = lazyOther; + } + + public Integer getSomeInteger() { + return someInteger; + } + + public void setSomeInteger(Integer someInteger) { + this.someInteger = someInteger; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithManyToOneSelfReference.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithManyToOneSelfReference.java new file mode 100644 index 000000000000..9d0cc97cf745 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithManyToOneSelfReference.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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.gambit; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +/** + * @author Steve Ebersole + */ +@Entity +@Table(name = "entity_m2o_selfref") +@SuppressWarnings("unused") +public class EntityWithManyToOneSelfReference { + private Integer id; + + // alphabetical + private String name; + private EntityWithManyToOneSelfReference other; + private Integer someInteger; + + EntityWithManyToOneSelfReference() { + } + + public EntityWithManyToOneSelfReference(Integer id, String name, Integer someInteger) { + this.id = id; + this.name = name; + this.someInteger = someInteger; + } + + public EntityWithManyToOneSelfReference( + Integer id, + String name, + Integer someInteger, + EntityWithManyToOneSelfReference other) { + this.id = id; + this.name = name; + this.someInteger = someInteger; + this.other = other; + } + + public EntityWithManyToOneSelfReference( + Integer id, + String name, + EntityWithManyToOneSelfReference other, + Integer someInteger) { + this.id = id; + this.name = name; + this.other = other; + this.someInteger = someInteger; + } + + @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; + } + + @ManyToOne + @JoinColumn + public EntityWithManyToOneSelfReference getOther() { + return other; + } + + public void setOther(EntityWithManyToOneSelfReference other) { + this.other = other; + } + + public Integer getSomeInteger() { + return someInteger; + } + + public void setSomeInteger(Integer someInteger) { + this.someInteger = someInteger; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithManyToOneWithoutJoinTable.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithManyToOneWithoutJoinTable.java new file mode 100644 index 000000000000..075c43a97f7a --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithManyToOneWithoutJoinTable.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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.gambit; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +/** + * @author Chris Cranford + */ +@Entity +public class EntityWithManyToOneWithoutJoinTable { + private Integer id; + private Integer someInteger; + private EntityWithOneToManyNotOwned owner; + + EntityWithManyToOneWithoutJoinTable() { + } + + public EntityWithManyToOneWithoutJoinTable(Integer id, Integer someInteger) { + this.id = id; + this.someInteger = someInteger; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Integer getSomeInteger() { + return someInteger; + } + + public void setSomeInteger(Integer someInteger) { + this.someInteger = someInteger; + } + + @ManyToOne + public EntityWithOneToManyNotOwned getOwner() { + return owner; + } + + public void setOwner(EntityWithOneToManyNotOwned owner) { + this.owner = owner; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithNonIdAttributeNamedId.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithNonIdAttributeNamedId.java new file mode 100644 index 000000000000..88dd98761d45 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithNonIdAttributeNamedId.java @@ -0,0 +1,37 @@ +/* + * 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.testing.orm.domain.gambit; + +import javax.persistence.Entity; +import javax.persistence.Id; + +/** + * @author Steve Ebersole + */ +@Entity +@SuppressWarnings("unused") +public class EntityWithNonIdAttributeNamedId { + private Integer pk; + private String id; + + @Id + public Integer getPk() { + return pk; + } + + public void setPk(Integer pk) { + this.pk = pk; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithNotAggregateId.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithNotAggregateId.java new file mode 100644 index 000000000000..2d885db1f3e2 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithNotAggregateId.java @@ -0,0 +1,99 @@ +/* + * 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.testing.orm.domain.gambit; + +import java.io.Serializable; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.IdClass; + +/** + * @author Andrea Boriero + */ +@Entity +@IdClass(EntityWithNotAggregateId.PK.class) +public class EntityWithNotAggregateId { + + @Id + private Integer value1; + + @Id + private String value2; + + private String data; + + public PK getId() { + return new PK( value1, value2 ); + } + + public void setId(PK id) { + this.value1 = id.getValue1(); + this.value2 = id.getValue2(); + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public static class PK implements Serializable { + private Integer value1; + private String value2; + + public PK() { + } + + public PK(Integer value1, String value2) { + this.value1 = value1; + this.value2 = value2; + } + + public Integer getValue1() { + return value1; + } + + public void setValue1(Integer value1) { + this.value1 = value1; + } + + public String getValue2() { + return value2; + } + + public void setValue2(String value2) { + this.value2 = value2; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + PK pk = (PK) o; + + if ( value1 != null ? !value1.equals( pk.value1 ) : pk.value1 != null ) { + return false; + } + return value2 != null ? value2.equals( pk.value2 ) : pk.value2 == null; + } + + @Override + public int hashCode() { + int result = value1 != null ? value1.hashCode() : 0; + result = 31 * result + ( value2 != null ? value2.hashCode() : 0 ); + return result; + } + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithOneToMany.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithOneToMany.java new file mode 100644 index 000000000000..9542d9387580 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithOneToMany.java @@ -0,0 +1,97 @@ +/* + * 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.testing.orm.domain.gambit; + +import java.sql.Types; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.hibernate.annotations.CollectionId; +import org.hibernate.annotations.GenericGenerator; +import org.hibernate.annotations.Type; + +import javax.persistence.CollectionTable; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.OneToMany; + +/** + * @author Andrea Boriero + */ +@Entity +@GenericGenerator(name="increment", strategy = "increment") +public class EntityWithOneToMany { + private Integer id; + + // alphabetical + private String name; + private Set others = new HashSet<>( ); + private List othersIdentifierBag = new ArrayList<>( ); + private Integer someInteger; + + public EntityWithOneToMany() { + } + + public EntityWithOneToMany(Integer id, String name, Integer someInteger) { + this.id = id; + this.name = name; + this.someInteger = someInteger; + } + + @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; + } + + @OneToMany(fetch = FetchType.LAZY) + public Set getOthers() { + return others; + } + + public void setOthers(Set others) { + this.others = others; + } + + public Integer getSomeInteger() { + return someInteger; + } + + public void setSomeInteger(Integer someInteger) { + this.someInteger = someInteger; + } + + public void addOther(SimpleEntity other) { + others.add( other ); + } + + @OneToMany + @CollectionTable(name = "idbag") + @CollectionId( column = @Column(name = "BAG_ID"), generator = "increment", type = @Type( type = "big_integer" ) ) + public List getOthersIdentifierBag() { + return othersIdentifierBag; + } + + public void setOthersIdentifierBag(List othersIdentifierBag) { + this.othersIdentifierBag = othersIdentifierBag; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithOneToManyNotOwned.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithOneToManyNotOwned.java new file mode 100644 index 000000000000..01103bc477fb --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithOneToManyNotOwned.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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.gambit; + +import java.util.ArrayList; +import java.util.List; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.OneToMany; + +/** + * @author Chris Cranford + */ +@Entity +public class EntityWithOneToManyNotOwned { + private Integer id; + private List children = new ArrayList<>(); + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + @OneToMany(mappedBy = "owner") + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } + + public void addChild(EntityWithManyToOneWithoutJoinTable child) { + child.setOwner( this ); + getChildren().add( child ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithOneToOne.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithOneToOne.java new file mode 100644 index 000000000000..e017ba092d26 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithOneToOne.java @@ -0,0 +1,67 @@ +/* + * 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.testing.orm.domain.gambit; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.OneToOne; + +/** + * @author Andrea Boriero + */ +@Entity +public class EntityWithOneToOne { + private Integer id; + + // alphabetical + private String name; + private SimpleEntity other; + private Integer someInteger; + + public EntityWithOneToOne() { + } + + public EntityWithOneToOne(Integer id, String name, Integer someInteger) { + this.id = id; + this.name = name; + this.someInteger = someInteger; + } + + @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 + public SimpleEntity getOther() { + return other; + } + + public void setOther(SimpleEntity other) { + this.other = other; + } + + public Integer getSomeInteger() { + return someInteger; + } + + public void setSomeInteger(Integer someInteger) { + this.someInteger = someInteger; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithOneToOneJoinTable.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithOneToOneJoinTable.java new file mode 100644 index 000000000000..e8eca7275762 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithOneToOneJoinTable.java @@ -0,0 +1,71 @@ +/* + * 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.testing.orm.domain.gambit; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinTable; +import javax.persistence.OneToOne; +import javax.persistence.Table; + +/** + * @author Andrea Boriero + */ +@Entity +@Table(name = "EntityWithOneToOneJoinTable") +public class EntityWithOneToOneJoinTable { + private Integer id; + + // alphabetical + private String name; + private SimpleEntity other; + private Integer someInteger; + + public EntityWithOneToOneJoinTable() { + } + + public EntityWithOneToOneJoinTable(Integer id, String name, Integer someInteger) { + this.id = id; + this.name = name; + this.someInteger = someInteger; + } + + @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 + @JoinTable(name = "Entity_SimpleEntity") + public SimpleEntity getOther() { + return other; + } + + public void setOther(SimpleEntity other) { + this.other = other; + } + + public Integer getSomeInteger() { + return someInteger; + } + + public void setSomeInteger(Integer someInteger) { + this.someInteger = someInteger; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithOneToOneSharingPrimaryKey.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithOneToOneSharingPrimaryKey.java new file mode 100644 index 000000000000..0706261b7e96 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithOneToOneSharingPrimaryKey.java @@ -0,0 +1,71 @@ +/* + * 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.testing.orm.domain.gambit; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.OneToOne; +import javax.persistence.PrimaryKeyJoinColumn; +import javax.persistence.Table; + +/** + * @author Andrea Boriero + */ +@Entity +@Table(name = "entity_o2o_sharepk") +public class EntityWithOneToOneSharingPrimaryKey { + private Integer id; + + // alphabetical + private String name; + private SimpleEntity other; + private Integer someInteger; + + public EntityWithOneToOneSharingPrimaryKey() { + } + + public EntityWithOneToOneSharingPrimaryKey(Integer id, String name, Integer someInteger) { + this.id = id; + this.name = name; + this.someInteger = someInteger; + } + + @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 + @PrimaryKeyJoinColumn + public SimpleEntity getOther() { + return other; + } + + public void setOther(SimpleEntity other) { + this.other = other; + } + + public Integer getSomeInteger() { + return someInteger; + } + + public void setSomeInteger(Integer someInteger) { + this.someInteger = someInteger; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EnumValue.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EnumValue.java new file mode 100644 index 000000000000..06097cf6ec9d --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EnumValue.java @@ -0,0 +1,47 @@ +/* + * 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.testing.orm.domain.gambit; + +/** + * @author Steve Ebersole + */ +public enum EnumValue { + ONE( "first" ), + TWO( "second" ), + THREE( "third" ); + + private final String code; + + EnumValue(String code) { + this.code = code; + } + + public String getCode() { + return code; + } + + public static EnumValue fromCode(String code) { + if ( code == null || code.isEmpty() ) { + return null; + } + + switch ( code ) { + case "first": { + return ONE; + } + case "second": { + return TWO; + } + case "third": { + return THREE; + } + default: { + throw new RuntimeException( "Could not convert enum code : " + code ); + } + } + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EnumValueConverter.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EnumValueConverter.java new file mode 100644 index 000000000000..c8899fd8eb21 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EnumValueConverter.java @@ -0,0 +1,24 @@ +/* + * 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.testing.orm.domain.gambit; + +import javax.persistence.AttributeConverter; + +/** + * @author Steve Ebersole + */ +public class EnumValueConverter implements AttributeConverter { + @Override + public String convertToDatabaseColumn(EnumValue domainValue) { + return domainValue == null ? null : domainValue.getCode(); + } + + @Override + public EnumValue convertToEntityAttribute(String dbData) { + return EnumValue.fromCode( dbData ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/GambitDomainModel.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/GambitDomainModel.java new file mode 100644 index 000000000000..966a29bebf5d --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/GambitDomainModel.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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.gambit; + +import org.hibernate.testing.orm.domain.AbstractDomainModelDescriptor; + +/** + * @author Steve Ebersole + */ +public class GambitDomainModel extends AbstractDomainModelDescriptor { + public static final GambitDomainModel INSTANCE = new GambitDomainModel(); + + public GambitDomainModel() { + super( + BasicEntity.class, + VersionedEntity.class, + Component.class, + EmbeddedIdEntity.class, + EntityOfArrays.class, + EntityOfBasics.class, + EntityOfComposites.class, + EntityOfDynamicComponent.class, + EntityOfLists.class, + EntityOfMaps.class, + EntityOfSets.class, + EntityWithLazyManyToOneSelfReference.class, + EntityWithLazyOneToOne.class, + EntityWithManyToOneJoinTable.class, + EntityWithManyToOneSelfReference.class, + EntityWithNonIdAttributeNamedId.class, + EntityWithAggregateId.class, + EntityWithOneToMany.class, + EntityWithOneToOne.class, + EntityWithOneToOneJoinTable.class, + EntityWithOneToOneSharingPrimaryKey.class, + Shirt.class, + SimpleEntity.class + ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/MutableValue.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/MutableValue.java new file mode 100644 index 000000000000..b933fdbb0e3d --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/MutableValue.java @@ -0,0 +1,33 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.gambit; + +import java.io.Serializable; + +/** + * A mutable (as in non-`@Immutable`) value. Mainly used for testing + * JPA AttributeConverter support for mutable domain values in regards + * to caching, dirty-checking, etc + */ +public class MutableValue implements Serializable { + private String state; + + public MutableValue() { + } + + public MutableValue(String state) { + this.state = state; + } + + public String getState() { + return state; + } + + public void setState(String state) { + this.state = state; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/Shirt.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/Shirt.java new file mode 100644 index 000000000000..7ff8e489e026 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/Shirt.java @@ -0,0 +1,109 @@ +/* + * 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.testing.orm.domain.gambit; + +import javax.persistence.AttributeConverter; +import javax.persistence.Column; +import javax.persistence.Convert; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.Id; + +/** + * @author Chris Cranford + */ +@Entity +public class Shirt { + @Id + private Integer id; + + @Convert(converter = ShirtStringToIntegerConverter.class) + private String data; + + @Enumerated + @Column(name = "shirt_size") + private Size size; + + @Enumerated(EnumType.STRING) + private Color color; + + public enum Size { + SMALL, + MEDIUM, + LARGE, + XLARGE + } + + public enum Color { + WHITE, + GREY, + BLACK, + BLUE, + TAN + } + + public static class ShirtStringToIntegerConverter implements AttributeConverter { + @Override + public Integer convertToDatabaseColumn(String attribute) { + if ( attribute != null ) { + if ( attribute.equalsIgnoreCase( "X" ) ) { + return 1; + } + else if ( attribute.equalsIgnoreCase( "Y" ) ) { + return 2; + } + } + return null; + } + + @Override + public String convertToEntityAttribute(Integer dbData) { + if ( dbData != null ) { + switch ( Integer.valueOf( dbData ) ) { + case 1: + return "X"; + case 2: + return "Y"; + } + } + return null; + } + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public Size getSize() { + return size; + } + + public void setSize(Size size) { + this.size = size; + } + + public Color getColor() { + return color; + } + + public void setColor(Color color) { + this.color = color; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/SimpleBasicSortComparator.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/SimpleBasicSortComparator.java new file mode 100644 index 000000000000..9849c3754e0a --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/SimpleBasicSortComparator.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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.gambit; + +import java.util.Comparator; + +/** + * @author Nathan Xu + */ +public class SimpleBasicSortComparator implements Comparator { + + @Override + public int compare(String s1, String s2) { + return String.CASE_INSENSITIVE_ORDER.compare( s1, s2 ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/SimpleComponent.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/SimpleComponent.java new file mode 100644 index 000000000000..b586ee849b1e --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/SimpleComponent.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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.gambit; + +import javax.persistence.Embeddable; + +/** + * @author Steve Ebersole + */ +@Embeddable +public class SimpleComponent { + private String anAttribute; + private String anotherAttribute; + + public SimpleComponent() { + } + + public SimpleComponent(String anAttribute, String anotherAttribute) { + this.anAttribute = anAttribute; + this.anotherAttribute = anotherAttribute; + } + + public String getAnAttribute() { + return anAttribute; + } + + public void setAnAttribute(String anAttribute) { + this.anAttribute = anAttribute; + } + + public String getAnotherAttribute() { + return anotherAttribute; + } + + public void setAnotherAttribute(String anotherAttribute) { + this.anotherAttribute = anotherAttribute; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/SimpleEntity.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/SimpleEntity.java new file mode 100644 index 000000000000..c6f062304501 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/SimpleEntity.java @@ -0,0 +1,119 @@ +/* + * 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.testing.orm.domain.gambit; + +import java.time.Instant; +import java.util.Date; + +import org.hibernate.annotations.NaturalId; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +/** + * @author Steve Ebersole + */ +@Entity +@Table(name = "SIMPLE_ENTITY") +public class SimpleEntity { + private Integer id; + + // NOTE : alphabetical + private Date someDate; + private Instant someInstant; + private Integer someInteger; + private Long someLong; + private String someString; + + public SimpleEntity() { + } + + public SimpleEntity( + Integer id, + String someString) { + this.id = id; + this.someString = someString; + } + + public SimpleEntity( + Integer id, + String someString, + Long someLong) { + this.id = id; + this.someString = someString; + this.someLong = someLong; + } + + public SimpleEntity( + Integer id, + Date someDate, + Instant someInstant, + Integer someInteger, + Long someLong, + String someString) { + this.id = id; + this.someDate = someDate; + this.someInstant = someInstant; + this.someInteger = someInteger; + this.someLong = someLong; + this.someString = someString; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getSomeString() { + return someString; + } + + public void setSomeString(String someString) { + this.someString = someString; + } + + @NaturalId + public Integer getSomeInteger() { + return someInteger; + } + + public void setSomeInteger(Integer someInteger) { + this.someInteger = someInteger; + } + + public Long getSomeLong() { + return someLong; + } + + public void setSomeLong(Long someLong) { + this.someLong = someLong; + } + + @Temporal( TemporalType.TIMESTAMP ) + public Date getSomeDate() { + return someDate; + } + + public void setSomeDate(Date someDate) { + this.someDate = someDate; + } + + public Instant getSomeInstant() { + return someInstant; + } + + public void setSomeInstant(Instant someInstant) { + this.someInstant = someInstant; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/VersionedEntity.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/VersionedEntity.java new file mode 100644 index 000000000000..68c8698b61cf --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/VersionedEntity.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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.gambit; + +import java.util.Objects; + +import org.hibernate.annotations.NaturalId; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Version; + +/** + * @author Chris Cranford + */ +@Entity +public class VersionedEntity { + @Id + private Integer id; + @Version + private Integer version; + @NaturalId + private String code; + private String data; + + public VersionedEntity() { + + } + + public VersionedEntity(Integer id, String data) { + this.id = id; + this.data = data; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + VersionedEntity that = (VersionedEntity) o; + return Objects.equals( id, that.id ) && + Objects.equals( data, that.data ); + } + + @Override + public int hashCode() { + return Objects.hash( id, data ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/Account.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/Account.java new file mode 100644 index 000000000000..1cc8b9d5ffee --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/Account.java @@ -0,0 +1,99 @@ +/* + * 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.testing.orm.domain.helpdesk; + +import javax.persistence.AttributeConverter; +import javax.persistence.Convert; +import javax.persistence.Converter; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.Id; + +/** + * @author Steve Ebersole + */ +@Entity +public class Account { + private Integer id; + + private Status loginStatus; + private Status systemAccessStatus; + private Status serviceStatus; + + public Account() { + } + + public Account( + Integer id, + Status loginStatus, + Status systemAccessStatus, + Status serviceStatus) { + this.id = id; + this.loginStatus = loginStatus; + this.systemAccessStatus = systemAccessStatus; + this.serviceStatus = serviceStatus; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + @Enumerated( EnumType.ORDINAL ) + public Status getLoginStatus() { + return loginStatus; + } + + public void setLoginStatus(Status loginStatus) { + this.loginStatus = loginStatus; + } + + @Enumerated( EnumType.STRING ) + public Status getSystemAccessStatus() { + return systemAccessStatus; + } + + public void setSystemAccessStatus(Status systemAccessStatus) { + this.systemAccessStatus = systemAccessStatus; + } + + @Convert( converter = ServiceStatusConverter.class ) + public Status getServiceStatus() { + return serviceStatus; + } + + public void setServiceStatus(Status serviceStatus) { + this.serviceStatus = serviceStatus; + } + + @Converter( autoApply = false ) + private static class ServiceStatusConverter implements AttributeConverter { + + @Override + public Integer convertToDatabaseColumn(Status attribute) { + if ( attribute == null ) { + return null; + } + + return attribute.getCode(); + } + + @Override + public Status convertToEntityAttribute(Integer dbData) { + if ( dbData == null ) { + return null; + } + + return Status.fromCode( dbData ); + } + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/HelpDeskDomainModel.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/HelpDeskDomainModel.java new file mode 100644 index 000000000000..632d7eefa6a0 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/HelpDeskDomainModel.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.testing.orm.domain.helpdesk; + +import org.hibernate.testing.orm.domain.AbstractDomainModelDescriptor; + +/** + * @author Steve Ebersole + */ +public class HelpDeskDomainModel extends AbstractDomainModelDescriptor { + public static final HelpDeskDomainModel INSTANCE = new HelpDeskDomainModel(); + + public HelpDeskDomainModel() { + super( + Status.class, + Account.class, + Ticket.class, + Incident.class + ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/Incident.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/Incident.java new file mode 100644 index 000000000000..2cae1d2aee8a --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/Incident.java @@ -0,0 +1,99 @@ +/* + * 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.testing.orm.domain.helpdesk; + +import java.time.Instant; + +import javax.persistence.ColumnResult; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.SqlResultSetMapping; + +/** + * @author Steve Ebersole + */ +@Entity +@SqlResultSetMapping( + name = "incident_summary", + columns = { + @ColumnResult( name = "id" ), + @ColumnResult( name = "description" ), + @ColumnResult( name = "reported", type = Instant.class ) + } +) +public class Incident { + private Integer id; + private String description; + + private Instant reported; + + private Instant effectiveStart; + private Instant effectiveEnd; + + public Incident() { + } + + public Incident(Integer id, String description, Instant reported) { + this.id = id; + this.description = description; + this.reported = reported; + } + + public Incident( + Integer id, + String description, + Instant reported, + Instant effectiveStart, + Instant effectiveEnd) { + this.id = id; + this.description = description; + this.reported = reported; + this.effectiveStart = effectiveStart; + this.effectiveEnd = effectiveEnd; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Instant getReported() { + return reported; + } + + public void setReported(Instant reported) { + this.reported = reported; + } + + public Instant getEffectiveStart() { + return effectiveStart; + } + + public void setEffectiveStart(Instant effectiveStart) { + this.effectiveStart = effectiveStart; + } + + public Instant getEffectiveEnd() { + return effectiveEnd; + } + + public void setEffectiveEnd(Instant effectiveEnd) { + this.effectiveEnd = effectiveEnd; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/Status.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/Status.java new file mode 100644 index 000000000000..484a4c7aed34 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/Status.java @@ -0,0 +1,31 @@ +/* + * 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.testing.orm.domain.helpdesk; + +public enum Status { + CREATED, + INITIALIZING, + ACTIVE, + INACTIVE; + + private final int code; + + Status() { + this.code = this.ordinal() + 1000; + } + + public int getCode() { + return code; + } + + public static Status fromCode(Integer code) { + if ( code == null ) { + return null; + } + return values()[ code - 1000 ]; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/Ticket.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/Ticket.java new file mode 100644 index 000000000000..1b4f6f5ab3dc --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/Ticket.java @@ -0,0 +1,36 @@ +/* + * 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.testing.orm.domain.helpdesk; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; + +/** + * @author Steve Ebersole + */ +@Entity +public class Ticket { + @Id + private Integer id; + + @Column(name = "ticket_key") + private String key; + + private String subject; + private String details; + +// private Incident associatedIncident; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/CardPayment.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/CardPayment.java new file mode 100644 index 000000000000..06a6e0e78df1 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/CardPayment.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.testing.orm.domain.retail; + +import javax.money.MonetaryAmount; + +import javax.persistence.Entity; + +/** + * @author Steve Ebersole + */ +@Entity +public class CardPayment extends Payment { + private Integer transactionId; + + public CardPayment() { + } + + public CardPayment(Integer id, Integer transactionId, MonetaryAmount amount) { + super( id,amount ); + this.transactionId = transactionId; + } + + public Integer getTransactionId() { + return transactionId; + } + + public void setTransactionId(Integer transactionId) { + this.transactionId = transactionId; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/CashPayment.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/CashPayment.java new file mode 100644 index 000000000000..d51c2affa54b --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/CashPayment.java @@ -0,0 +1,24 @@ +/* + * 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.testing.orm.domain.retail; + +import javax.money.MonetaryAmount; + +import javax.persistence.Entity; + +/** + * @author Steve Ebersole + */ +@Entity +public class CashPayment extends Payment { + public CashPayment() { + } + + public CashPayment(Integer id, MonetaryAmount amount) { + super( id, amount ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/DomesticVendor.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/DomesticVendor.java new file mode 100644 index 000000000000..e0be8c01cb63 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/DomesticVendor.java @@ -0,0 +1,24 @@ +/* + * 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.testing.orm.domain.retail; + +import javax.persistence.DiscriminatorValue; +import javax.persistence.Entity; + +/** + * @author Steve Ebersole + */ +@Entity +@DiscriminatorValue( "domestic" ) +public class DomesticVendor extends Vendor { + public DomesticVendor() { + } + + public DomesticVendor(Integer id, String name, String billingEntity) { + super( id, name, billingEntity ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/ForeignVendor.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/ForeignVendor.java new file mode 100644 index 000000000000..1639bfaf4103 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/ForeignVendor.java @@ -0,0 +1,24 @@ +/* + * 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.testing.orm.domain.retail; + +import javax.persistence.DiscriminatorValue; +import javax.persistence.Entity; + +/** + * @author Steve Ebersole + */ +@Entity +@DiscriminatorValue( "foreign" ) +public class ForeignVendor extends Vendor { + public ForeignVendor() { + } + + public ForeignVendor(Integer id, String name, String billingEntity) { + super( id, name, billingEntity ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/LineItem.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/LineItem.java new file mode 100644 index 000000000000..2a85e9c6e3dc --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/LineItem.java @@ -0,0 +1,73 @@ +/* + * 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.testing.orm.domain.retail; + +import javax.money.MonetaryAmount; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; + +/** + * @author Steve Ebersole + */ +@Entity +public class LineItem { + private Integer id; + private Product product; + + private int quantity; + private MonetaryAmount subTotal; + + private Order order; + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + @ManyToOne + @JoinColumn( name = "product_id" ) + public Product getProduct() { + return product; + } + + public void setProduct(Product product) { + this.product = product; + } + + public int getQuantity() { + return quantity; + } + + public void setQuantity(int quantity) { + this.quantity = quantity; + } + + public MonetaryAmount getSubTotal() { + return subTotal; + } + + public void setSubTotal(MonetaryAmount subTotal) { + this.subTotal = subTotal; + } + + @ManyToOne + @JoinColumn( name = "order_id" ) + public Order getOrder() { + return order; + } + + public void setOrder(Order order) { + this.order = order; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/Name.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/Name.java new file mode 100644 index 000000000000..da5f4eff9f9f --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/Name.java @@ -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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.retail; + +import javax.persistence.Embeddable; + +/** + * @author Steve Ebersole + */ +@Embeddable +@SuppressWarnings("unused") +public class Name { + private String familyName; + private String familiarName; + + private String prefix; + private String suffix; + + public Name() { + } + + public Name(String familyName, String familiarName) { + this.familyName = familyName; + this.familiarName = familiarName; + } + + public Name(String familyName, String familiarName, String suffix) { + this.familyName = familyName; + this.familiarName = familiarName; + this.suffix = suffix; + } + + public Name(String familyName, String familiarName, String prefix, String suffix) { + this.familyName = familyName; + this.familiarName = familiarName; + this.prefix = prefix; + this.suffix = suffix; + } + + public String getFamilyName() { + return familyName; + } + + public void setFamilyName(String familyName) { + this.familyName = familyName; + } + + public String getFamiliarName() { + return familiarName; + } + + public void setFamiliarName(String familiarName) { + this.familiarName = familiarName; + } + + public String getPrefix() { + return prefix; + } + + public void setPrefix(String prefix) { + this.prefix = prefix; + } + + public String getSuffix() { + return suffix; + } + + public void setSuffix(String suffix) { + this.suffix = suffix; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/Order.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/Order.java new file mode 100644 index 000000000000..aea13d0d92af --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/Order.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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.retail; + +import java.time.Instant; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +/** + * @author Steve Ebersole + */ +@Entity +@Table( name = "orders") +public class Order { + private Integer id; + private Instant transacted; + + private Payment payment; + private SalesAssociate salesAssociate; + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Instant getTransacted() { + return transacted; + } + + public void setTransacted(Instant transacted) { + this.transacted = transacted; + } + + @ManyToOne + @JoinColumn(name = "payment_id") + public Payment getPayment() { + return payment; + } + + public void setPayment(Payment payment) { + this.payment = payment; + } + + @ManyToOne + @JoinColumn(name = "associate_id") + public SalesAssociate getSalesAssociate() { + return salesAssociate; + } + + public void setSalesAssociate(SalesAssociate salesAssociate) { + this.salesAssociate = salesAssociate; + } + +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/Payment.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/Payment.java new file mode 100644 index 000000000000..d4dcede7cadc --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/Payment.java @@ -0,0 +1,51 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.retail; + +import javax.money.MonetaryAmount; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.Table; + +/** + * @author Steve Ebersole + */ +@Entity +@Inheritance(strategy = InheritanceType.JOINED) +@Table( name = "payments" ) +public abstract class Payment { + private Integer id; + private MonetaryAmount amount; + + public Payment() { + } + + public Payment(Integer id, MonetaryAmount amount) { + this.id = id; + this.amount = amount; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public MonetaryAmount getAmount() { + return amount; + } + + public void setAmount(MonetaryAmount amount) { + this.amount = amount; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/Product.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/Product.java new file mode 100644 index 000000000000..a59c61b3cd9b --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/Product.java @@ -0,0 +1,82 @@ +/* + * 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.testing.orm.domain.retail; + +import java.util.UUID; +import javax.money.MonetaryAmount; + +import org.hibernate.annotations.NaturalId; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; + +/** + * @author Steve Ebersole + */ +@Entity +public class Product { + private Integer id; + private UUID sku; + + private Vendor vendor; + + private MonetaryAmount currentSellPrice; + + public Product() { + } + + public Product(Integer id, UUID sku, Vendor vendor) { + this.id = id; + this.sku = sku; + this.vendor = vendor; + } + + public Product(Integer id, UUID sku, Vendor vendor, MonetaryAmount currentSellPrice) { + this.id = id; + this.sku = sku; + this.vendor = vendor; + this.currentSellPrice = currentSellPrice; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + @ManyToOne + @JoinColumn + public Vendor getVendor() { + return vendor; + } + + public void setVendor(Vendor vendor) { + this.vendor = vendor; + } + + @NaturalId + public UUID getSku() { + return sku; + } + + public void setSku(UUID sku) { + this.sku = sku; + } + + public MonetaryAmount getCurrentSellPrice() { + return currentSellPrice; + } + + public void setCurrentSellPrice(MonetaryAmount currentSellPrice) { + this.currentSellPrice = currentSellPrice; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/RetailDomainModel.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/RetailDomainModel.java new file mode 100644 index 000000000000..255f650ef0fa --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/RetailDomainModel.java @@ -0,0 +1,61 @@ +/* + * 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.testing.orm.domain.retail; + +import java.util.EnumSet; + +import org.hibernate.boot.MetadataSources; + +import org.hibernate.testing.orm.domain.AbstractDomainModelDescriptor; +import org.hibernate.testing.orm.domain.MappingFeature; +import org.hibernate.testing.orm.domain.MonetaryAmountConverter; + +import static org.hibernate.testing.orm.domain.MappingFeature.CONVERTER; +import static org.hibernate.testing.orm.domain.MappingFeature.EMBEDDABLE; +import static org.hibernate.testing.orm.domain.MappingFeature.JOINED_INHERIT; +import static org.hibernate.testing.orm.domain.MappingFeature.JOIN_COLUMN; +import static org.hibernate.testing.orm.domain.MappingFeature.MANY_ONE; +import static org.hibernate.testing.orm.domain.MappingFeature.SECONDARY_TABLE; + +/** + * @author Steve Ebersole + */ +public class RetailDomainModel extends AbstractDomainModelDescriptor { + public static final RetailDomainModel INSTANCE = new RetailDomainModel(); + + public RetailDomainModel() { + super( + MonetaryAmountConverter.class, + SalesAssociate.class, + Vendor.class, + DomesticVendor.class, + ForeignVendor.class, + Product.class, + Order.class, + LineItem.class, + Payment.class, + CashPayment.class, + CardPayment.class + ); + } + + public static void applyRetailModel(MetadataSources sources) { + INSTANCE.applyDomainModel( sources ); + } + + @Override + public EnumSet getMappingFeaturesUsed() { + return EnumSet.of( + CONVERTER, + EMBEDDABLE, + MANY_ONE, + JOIN_COLUMN, + SECONDARY_TABLE, + JOINED_INHERIT + ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/SalesAssociate.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/SalesAssociate.java new file mode 100644 index 000000000000..6e22d4f0a752 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/SalesAssociate.java @@ -0,0 +1,47 @@ +/* + * 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.testing.orm.domain.retail; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +/** + * @author Steve Ebersole + */ +@Entity +@Table( name = "ASSOCIATE") +public class SalesAssociate { + private Integer id; + + private Name name; + + public SalesAssociate() { + } + + public SalesAssociate(Integer id, Name name) { + this.id = id; + this.name = name; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Name getName() { + return name; + } + + public void setName(Name name) { + this.name = name; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/Vendor.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/Vendor.java new file mode 100644 index 000000000000..2e499ec1e150 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/Vendor.java @@ -0,0 +1,61 @@ +/* + * 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.testing.orm.domain.retail; + +import javax.persistence.DiscriminatorColumn; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.SecondaryTable; + +/** + * @author Steve Ebersole + */ +@Entity +@Inheritance( strategy = InheritanceType.SINGLE_TABLE ) +@DiscriminatorColumn( name = "vendor_type" ) +@SecondaryTable(name = "vendor_supp") +public class Vendor { + private Integer id; + private String name; + private String billingEntity; + + public Vendor() { + } + + public Vendor(Integer id, String name, String billingEntity) { + this.id = id; + this.name = name; + this.billingEntity = billingEntity; + } + + @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 String getBillingEntity() { + return billingEntity; + } + + public void setBillingEntity(String billingEntity) { + this.billingEntity = billingEntity; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/jpa/PersistenceUnitDescriptorAdapter.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/jpa/PersistenceUnitDescriptorAdapter.java new file mode 100644 index 000000000000..ac085485efb3 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/jpa/PersistenceUnitDescriptorAdapter.java @@ -0,0 +1,116 @@ +/* + * 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.testing.orm.jpa; + +import java.net.URL; +import java.util.Collections; +import java.util.List; +import java.util.Properties; +import javax.sql.DataSource; + +import org.hibernate.bytecode.enhance.spi.EnhancementContext; +import org.hibernate.jpa.HibernatePersistenceProvider; +import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor; + +import javax.persistence.SharedCacheMode; +import javax.persistence.ValidationMode; +import javax.persistence.spi.PersistenceUnitTransactionType; + +/** + * @author Steve Ebersole + */ +public class PersistenceUnitDescriptorAdapter implements PersistenceUnitDescriptor { + private final String name = "persistenceUnitDescriptorAdapter@" + System.identityHashCode( this ); + private Properties properties; + + @Override + public String getName() { + return name; + } + + @Override + public boolean isUseQuotedIdentifiers() { + return false; + } + + @Override + public String getProviderClassName() { + return HibernatePersistenceProvider.class.getName(); + } + + @Override + public PersistenceUnitTransactionType getTransactionType() { + return null; + } + + @Override + public DataSource getJtaDataSource() { + return null; + } + + @Override + public DataSource getNonJtaDataSource() { + return null; + } + + @Override + public List getMappingFileNames() { + return Collections.emptyList(); + } + + @Override + public List getJarFileUrls() { + return Collections.emptyList(); + } + + @Override + public URL getPersistenceUnitRootUrl() { + return null; + } + + @Override + public List getManagedClassNames() { + return Collections.emptyList(); + } + + @Override + public boolean isExcludeUnlistedClasses() { + return false; + } + + @Override + public SharedCacheMode getSharedCacheMode() { + return null; + } + + @Override + public ValidationMode getValidationMode() { + return null; + } + + @Override + public Properties getProperties() { + if ( properties == null ) { + properties = new Properties(); + } + return properties; + } + + @Override + public ClassLoader getClassLoader() { + return Thread.currentThread().getContextClassLoader(); + } + + @Override + public ClassLoader getTempClassLoader() { + return null; + } + + @Override + public void pushClassTransformer(EnhancementContext enhancementContext) { + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/jpa/PersistenceUnitInfoAdapter.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/jpa/PersistenceUnitInfoAdapter.java new file mode 100644 index 000000000000..36ecb8332305 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/jpa/PersistenceUnitInfoAdapter.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.testing.orm.jpa; + +import java.net.URL; +import java.util.Collections; +import java.util.List; +import java.util.Properties; +import javax.sql.DataSource; + +import org.hibernate.jpa.HibernatePersistenceProvider; + +import javax.persistence.SharedCacheMode; +import javax.persistence.ValidationMode; +import javax.persistence.spi.ClassTransformer; +import javax.persistence.spi.PersistenceUnitInfo; +import javax.persistence.spi.PersistenceUnitTransactionType; + +/** + * Implementation of {@link PersistenceUnitInfo} for testing use. + * + * Expected usage is to override methods relevant to their specific tests. + * + * See {@link PersistenceUnitInfoImpl} for a more bean-like implementation + * + * @author Steve Ebersole + */ +public class PersistenceUnitInfoAdapter implements PersistenceUnitInfo { + private final String name = "persistenceUnitInfoAdapter@" + System.identityHashCode( this ); + private Properties properties; + + public String getPersistenceUnitName() { + return name; + } + + public String getPersistenceProviderClassName() { + return HibernatePersistenceProvider.class.getName(); + } + + public PersistenceUnitTransactionType getTransactionType() { + return null; + } + + public DataSource getJtaDataSource() { + return null; + } + + public DataSource getNonJtaDataSource() { + return null; + } + + public List getMappingFileNames() { + return Collections.emptyList(); + } + + public List getJarFileUrls() { + return Collections.emptyList(); + } + + public URL getPersistenceUnitRootUrl() { + return null; + } + + public List getManagedClassNames() { + return Collections.emptyList(); + } + + public boolean excludeUnlistedClasses() { + return false; + } + + public SharedCacheMode getSharedCacheMode() { + return null; + } + + public ValidationMode getValidationMode() { + return null; + } + + public Properties getProperties() { + if ( properties == null ) { + properties = new Properties(); + } + return properties; + } + + public String getPersistenceXMLSchemaVersion() { + return null; + } + + public ClassLoader getClassLoader() { + return Thread.currentThread().getContextClassLoader(); + } + + public void addTransformer(ClassTransformer transformer) { + } + + public ClassLoader getNewTempClassLoader() { + return Thread.currentThread().getContextClassLoader(); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/jpa/PersistenceUnitInfoImpl.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/jpa/PersistenceUnitInfoImpl.java new file mode 100644 index 000000000000..4e5d452e7bdc --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/jpa/PersistenceUnitInfoImpl.java @@ -0,0 +1,163 @@ +/* + * 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.testing.orm.jpa; + +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Properties; +import javax.sql.DataSource; + +import org.hibernate.jpa.HibernatePersistenceProvider; + +import javax.persistence.SharedCacheMode; +import javax.persistence.ValidationMode; +import javax.persistence.spi.ClassTransformer; +import javax.persistence.spi.PersistenceUnitInfo; +import javax.persistence.spi.PersistenceUnitTransactionType; + +/** + * Implementation of {@link PersistenceUnitInfo} for testing use. + * + * This implementation provides a bean-like contract for providing PU information. + * + * See {@link PersistenceUnitInfoAdapter} for an override-based solution + * + * @author Steve Ebersole + */ +public class PersistenceUnitInfoImpl implements PersistenceUnitInfo { + private final String name; + private final Properties properties = new Properties(); + + private PersistenceUnitTransactionType transactionType; + private SharedCacheMode cacheMode; + private ValidationMode validationMode; + + private List mappingFiles; + private List managedClassNames; + private boolean excludeUnlistedClasses; + + public PersistenceUnitInfoImpl(String name) { + this.name = name; + } + + @Override + public String getPersistenceUnitName() { + return name; + } + + @Override + public Properties getProperties() { + return properties; + } + + @Override + public String getPersistenceProviderClassName() { + return HibernatePersistenceProvider.class.getName(); + } + + @Override + public PersistenceUnitTransactionType getTransactionType() { + return transactionType; + } + + public void setTransactionType(PersistenceUnitTransactionType transactionType) { + this.transactionType = transactionType; + } + + @Override + public SharedCacheMode getSharedCacheMode() { + return cacheMode; + } + + public void setCacheMode(SharedCacheMode cacheMode) { + this.cacheMode = cacheMode; + } + + @Override + public ValidationMode getValidationMode() { + return validationMode; + } + + public void setValidationMode(ValidationMode validationMode) { + this.validationMode = validationMode; + } + + @Override + public List getMappingFileNames() { + return mappingFiles == null ? Collections.emptyList() : mappingFiles; + } + + public void applyMappingFiles(String... mappingFiles) { + if ( this.mappingFiles == null ) { + this.mappingFiles = new ArrayList<>(); + } + Collections.addAll( this.mappingFiles, mappingFiles ); + } + + @Override + public List getManagedClassNames() { + return managedClassNames == null ? Collections.emptyList() : managedClassNames; + } + + public void applyManagedClassNames(String... managedClassNames) { + if ( this.managedClassNames == null ) { + this.managedClassNames = new ArrayList<>(); + } + Collections.addAll( this.managedClassNames, managedClassNames ); + } + + @Override + public boolean excludeUnlistedClasses() { + return excludeUnlistedClasses; + } + + public void setExcludeUnlistedClasses(boolean excludeUnlistedClasses) { + this.excludeUnlistedClasses = excludeUnlistedClasses; + } + + @Override + public String getPersistenceXMLSchemaVersion() { + return null; + } + + @Override + public DataSource getJtaDataSource() { + return null; + } + + @Override + public DataSource getNonJtaDataSource() { + return null; + } + + @Override + public List getJarFileUrls() { + return null; + } + + @Override + public URL getPersistenceUnitRootUrl() { + return null; + } + + @Override + public ClassLoader getClassLoader() { + return null; + } + + @Override + public void addTransformer(ClassTransformer transformer) { + + } + + @Override + public ClassLoader getNewTempClassLoader() { + return null; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/AbstractEntityManagerFactoryScope.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/AbstractEntityManagerFactoryScope.java new file mode 100644 index 000000000000..a61c35e05b20 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/AbstractEntityManagerFactoryScope.java @@ -0,0 +1,158 @@ +/* + * 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.testing.orm.junit; + +import java.util.function.Consumer; +import java.util.function.Function; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.resource.jdbc.spi.StatementInspector; + +import org.hibernate.testing.orm.transaction.TransactionUtil; +import org.junit.jupiter.api.extension.ExtensionContext; + +import org.jboss.logging.Logger; + +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; + +abstract class AbstractEntityManagerFactoryScope implements EntityManagerFactoryScope, ExtensionContext.Store.CloseableResource { + private static final Logger log = Logger.getLogger( EntityManagerFactoryScope.class ); + + protected EntityManagerFactory emf; + protected boolean active = true; + + @Override + public EntityManagerFactory getEntityManagerFactory() { + if ( emf == null ) { + if ( !active ) { + throw new IllegalStateException( "EntityManagerFactoryScope is no longer active" ); + } + + log.debug( "Creating EntityManagerFactory" ); + emf = createEntityManagerFactory(); + } + + return emf; + } + + protected abstract EntityManagerFactory createEntityManagerFactory(); + + @Override + public StatementInspector getStatementInspector() { + return getEntityManagerFactory().unwrap( SessionFactoryImplementor.class ) + .getSessionFactoryOptions() + .getStatementInspector(); + } + + @Override + public T getStatementInspector(Class type) { + //noinspection unchecked + return (T) getStatementInspector(); + } + + @Override + public void close() { + if ( !active ) { + return; + } + + log.debug( "Closing SessionFactoryScope" ); + + active = false; + releaseEntityManagerFactory(); + } + + public void releaseEntityManagerFactory() { + if ( emf != null ) { + log.debug( "Releasing SessionFactory" ); + + try { + emf.close(); + } + catch (Exception e) { + log.warn( "Error closing EMF", e ); + } + finally { + emf = null; + } + } + } + + @Override + public void inEntityManager(Consumer action) { + log.trace( "#inEntityManager(Consumer)" ); + + try (SessionImplementor session = getEntityManagerFactory().createEntityManager() + .unwrap( SessionImplementor.class )) { + log.trace( "EntityManager opened, calling action" ); + action.accept( session ); + } + finally { + log.trace( "EntityManager close - auto-close block" ); + } + } + + @Override + public T fromEntityManager(Function action) { + log.trace( "#fromEntityManager(Function)" ); + + try (SessionImplementor session = getEntityManagerFactory().createEntityManager() + .unwrap( SessionImplementor.class )) { + log.trace( "EntityManager opened, calling action" ); + return action.apply( session ); + } + finally { + log.trace( "EntityManager close - auto-close block" ); + } + } + + @Override + public void inTransaction(Consumer action) { + log.trace( "#inTransaction(Consumer)" ); + + try (SessionImplementor session = getEntityManagerFactory().createEntityManager() + .unwrap( SessionImplementor.class )) { + log.trace( "EntityManager opened, calling action" ); + inTransaction( session, action ); + } + finally { + log.trace( "EntityManager close - auto-close block" ); + } + } + + @Override + public T fromTransaction(Function action) { + log.trace( "#fromTransaction(Function)" ); + + try (SessionImplementor session = getEntityManagerFactory().createEntityManager() + .unwrap( SessionImplementor.class )) { + log.trace( "EntityManager opened, calling action" ); + return fromTransaction( session, action ); + } + finally { + log.trace( "EntityManager close - auto-close block" ); + } + } + + @Override + public void inTransaction(EntityManager entityManager, Consumer action) { + log.trace( "inTransaction(EntityManager,Consumer)" ); + TransactionUtil.inTransaction( entityManager, action ); + } + + @Override + public T fromTransaction(EntityManager entityManager, Function action) { + log.trace( "fromTransaction(EntityManager,Function)" ); + + final SessionImplementor session = entityManager.unwrap( SessionImplementor.class ); + return TransactionUtil.fromTransaction( session, action ); + } + +} + diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/BaseSessionFactoryFunctionalTest.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/BaseSessionFactoryFunctionalTest.java new file mode 100644 index 000000000000..05d7b6c50a3e --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/BaseSessionFactoryFunctionalTest.java @@ -0,0 +1,376 @@ +/* + * 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.testing.orm.junit; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Iterator; +import java.util.Locale; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.hibernate.Session; +import org.hibernate.SessionBuilder; +import org.hibernate.Transaction; +import org.hibernate.boot.Metadata; +import org.hibernate.boot.MetadataBuilder; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.boot.spi.MetadataImplementor; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Environment; +import org.hibernate.dialect.Dialect; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.mapping.Collection; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.Property; +import org.hibernate.mapping.RootClass; +import org.hibernate.mapping.SimpleValue; + +import org.hibernate.testing.jdbc.SharedDriverManagerConnectionProviderImpl; +import org.hibernate.testing.transaction.TransactionUtil; +import org.junit.jupiter.api.AfterEach; + +import org.jboss.logging.Logger; + +/** + * Template (GoF pattern) based abstract class for tests bridging the legacy + * approach of SessionFactory building as a test fixture + * + * @author Steve Ebersole + */ +@SessionFactoryFunctionalTesting +public abstract class BaseSessionFactoryFunctionalTest + implements ServiceRegistryProducer, ServiceRegistryScopeAware, + DomainModelProducer, DomainModelScopeAware, + SessionFactoryProducer, SessionFactoryScopeAware { + + protected static final Dialect DIALECT = DialectContext.getDialect(); + + protected static final Class[] NO_CLASSES = new Class[0]; + protected static final String[] NO_MAPPINGS = new String[0]; + + private static final Logger log = Logger.getLogger( BaseSessionFactoryFunctionalTest.class ); + + private ServiceRegistryScope registryScope; + private DomainModelScope modelScope; + private SessionFactoryScope sessionFactoryScope; + + private final ExecutorService executorService = Executors.newSingleThreadExecutor(); + + protected SessionFactoryScope sessionFactoryScope() { + return sessionFactoryScope; + } + + protected SessionFactoryImplementor sessionFactory() { + return sessionFactoryScope.getSessionFactory(); + } + + protected MetadataImplementor getMetadata(){ + return modelScope.getDomainModel(); + } + + @Override + public StandardServiceRegistry produceServiceRegistry(StandardServiceRegistryBuilder ssrBuilder) { + ssrBuilder.applySetting( AvailableSettings.HBM2DDL_AUTO, exportSchema() ? "create-drop" : "none" ); + if ( !Environment.getProperties().containsKey( Environment.CONNECTION_PROVIDER ) ) { + ssrBuilder.applySetting( + AvailableSettings.CONNECTION_PROVIDER, + SharedDriverManagerConnectionProviderImpl.getInstance() + ); + } + applySettings( ssrBuilder ); + return ssrBuilder.build(); + } + + @Override + public void prepareBootstrapRegistryBuilder(BootstrapServiceRegistryBuilder bsrb) { + + } + + protected boolean exportSchema() { + return true; + } + + protected void applySettings(StandardServiceRegistryBuilder builder) { + } + + @Override + public void injectServiceRegistryScope(ServiceRegistryScope registryScope) { + this.registryScope = registryScope; + } + + @Override + public MetadataImplementor produceModel(StandardServiceRegistry serviceRegistry) { + MetadataSources metadataSources = new MetadataSources( serviceRegistry ); + MetadataBuilder metadataBuilder = metadataSources.getMetadataBuilder(); + applyMetadataBuilder( metadataBuilder ); + applyMetadataSources( metadataSources ); + final MetadataImplementor metadata = (MetadataImplementor) metadataBuilder.build(); + if ( !overrideCacheStrategy() || getCacheConcurrencyStrategy() == null ) { + return metadata; + } + + applyCacheSettings( metadata ); + + return metadata; + } + + protected final void applyCacheSettings(Metadata metadata) { + for ( PersistentClass entityBinding : metadata.getEntityBindings() ) { + if ( entityBinding.isInherited() ) { + continue; + } + + boolean hasLob = false; + + final Iterator props = entityBinding.getPropertyClosureIterator(); + while ( props.hasNext() ) { + final Property prop = (Property) props.next(); + if ( prop.getValue().isSimpleValue() ) { + if ( isLob( (SimpleValue) prop.getValue() ) ) { + hasLob = true; + break; + } + } + } + + if ( !hasLob ) { + ( (RootClass) entityBinding ).setCacheConcurrencyStrategy( getCacheConcurrencyStrategy() ); + entityBinding.setCached( true ); + } + } + + for ( Collection collectionBinding : metadata.getCollectionBindings() ) { + boolean isLob = false; + + if ( collectionBinding.getElement().isSimpleValue() ) { + isLob = isLob( (SimpleValue) collectionBinding.getElement() ); + } + + if ( !isLob ) { + collectionBinding.setCacheConcurrencyStrategy( getCacheConcurrencyStrategy() ); + } + } + } + + protected boolean overrideCacheStrategy() { + return true; + } + + protected String getCacheConcurrencyStrategy() { + return null; + } + + protected void applyMetadataBuilder(MetadataBuilder metadataBuilder) { + + } + + protected void applyMetadataSources(MetadataSources metadataSources) { + + for ( Class annotatedClass : getAnnotatedClasses() ) { + metadataSources.addAnnotatedClass( annotatedClass ); + } + String[] xmlFiles = getOrmXmlFiles(); + if ( xmlFiles != null ) { + for ( String xmlFile : xmlFiles ) { + try ( InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream( xmlFile ) ) { + metadataSources.addInputStream( is ); + } + catch (IOException e) { + throw new IllegalArgumentException( e ); + } + } + } + } + + protected Class[] getAnnotatedClasses() { + return NO_CLASSES; + } + + protected String[] getOrmXmlFiles() { + return NO_MAPPINGS; + } + + @Override + public void injectTestModelScope(DomainModelScope modelScope) { + this.modelScope = modelScope; + } + + @Override + public SessionFactoryImplementor produceSessionFactory(MetadataImplementor model) { + log.trace( "Producing SessionFactory" ); + final SessionFactoryBuilder sfBuilder = model.getSessionFactoryBuilder(); + configure( sfBuilder ); + final SessionFactoryImplementor factory = (SessionFactoryImplementor) sfBuilder.build(); + sessionFactoryBuilt( factory ); + return factory; + } + + protected void configure(SessionFactoryBuilder builder) { + } + + protected void sessionFactoryBuilt(SessionFactoryImplementor factory) { + } + + @Override + public void injectSessionFactoryScope(SessionFactoryScope scope) { + sessionFactoryScope = scope; + } + + // there is a chicken-egg problem here where the +// @AfterAll +// public void dropDatabase() { +// final SchemaManagementToolCoordinator.ActionGrouping actions = SchemaManagementToolCoordinator.ActionGrouping.interpret( +// registry.getService( ConfigurationService.class ).getSettings() +// ); +// +// final boolean needsDropped = this.model != null && ( exportSchema() || actions.getDatabaseAction() != Action.NONE ); +// +// if ( needsDropped ) { +// // atm we do not expose the (runtime) DatabaseModel from the SessionFactory so we +// // need to recreate it from the boot model. +// // +// // perhaps we should expose it from SF? +// final DatabaseModel databaseModel = Helper.buildDatabaseModel( registry, model ); +// new SchemaExport( databaseModel, registry ).drop( EnumSet.of( TargetType.DATABASE ) ); +// } +// } + + @AfterEach + public final void afterTest() { + if ( isCleanupTestDataRequired() ) { + cleanupTestData(); + } + } + + protected boolean isCleanupTestDataRequired() { + return false; + } + + protected void cleanupTestData() { + inTransaction( + session -> + getMetadata().getEntityBindings().forEach( + entityType -> session.createQuery( "delete from " + entityType.getEntityName() ).executeUpdate() + ) + + ); + } + + protected void inTransaction(Consumer action) { + sessionFactoryScope().inTransaction( action ); + } + + protected T fromTransaction(Function action) { + return sessionFactoryScope().fromTransaction( action ); + } + + protected void inSession(Consumer action){ + sessionFactoryScope.inSession( action ); + } + + protected T fromSession(Function action){ + return sessionFactoryScope.fromSession( action ); + } + + protected Dialect getDialect(){ + return DialectContext.getDialect(); + } + + private static boolean isLob(SimpleValue value) { + final String typeName = value.getTypeName(); + if ( typeName != null ) { + String significantTypeNamePart = typeName.substring( typeName.lastIndexOf( '.' ) + 1 ) + .toLowerCase( Locale.ROOT ); + switch ( significantTypeNamePart ) { + case "blob": + case "blobtype": + case "clob": + case "clobtype": + case "nclob": + case "nclobtype": + return true; + } + } + return false; + } + + protected Future executeAsync(Runnable callable) { + return executorService.submit(callable); + } + + protected void executeSync(Runnable callable) { + try { + executeAsync( callable ).get(); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + catch (ExecutionException e) { + throw new RuntimeException( e.getCause() ); + } + } + + /** + * Execute function in a Hibernate transaction without return value + * + * @param sessionBuilderSupplier SessionFactory supplier + * @param function function + */ + public static void doInHibernateSessionBuilder( + Supplier sessionBuilderSupplier, + TransactionUtil.HibernateTransactionConsumer function) { + Session session = null; + Transaction txn = null; + try { + session = sessionBuilderSupplier.get().openSession(); + function.beforeTransactionCompletion(); + txn = session.beginTransaction(); + + function.accept( session ); + if ( !txn.getRollbackOnly() ) { + txn.commit(); + } + else { + try { + txn.rollback(); + } + catch (Exception e) { + log.error( "Rollback failure", e ); + } + } + } + catch ( Throwable t ) { + if ( txn != null && txn.isActive() ) { + try { + txn.rollback(); + } + catch (Exception e) { + log.error( "Rollback failure", e ); + } + } + throw t; + } + finally { + function.afterTransactionCompletion(); + if ( session != null ) { + session.close(); + } + } + } + +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/BaseUnitTest.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/BaseUnitTest.java new file mode 100644 index 000000000000..394b5c914365 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/BaseUnitTest.java @@ -0,0 +1,28 @@ +/* + * 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.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExtendWith; + +@Inherited +@Target( ElementType.TYPE ) +@Retention( RetentionPolicy.RUNTIME ) +@TestInstance( TestInstance.Lifecycle.PER_CLASS ) + +@ExtendWith( FailureExpectedExtension.class ) +@ExtendWith( ExpectedExceptionExtension.class ) +@ExtendWith( DialectFilterExtension.class ) +public @interface BaseUnitTest { + +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/BootstrapServiceRegistry.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/BootstrapServiceRegistry.java new file mode 100644 index 000000000000..1b04d38649a6 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/BootstrapServiceRegistry.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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.hibernate.integrator.spi.Integrator; + +/** + * Used to define the bootstrap ServiceRegistry to be used for testing. + */ +@Inherited +@Target( ElementType.TYPE ) +@Retention( RetentionPolicy.RUNTIME ) + +@ServiceRegistryFunctionalTesting +public @interface BootstrapServiceRegistry { + + Class[] integrators() default {}; + + JavaService[] javaServices() default {}; + + @interface JavaService { + /** + * Logically `?` is `T` + */ + Class role(); + /** + * Logically `?` is `S extends T` + */ + Class impl(); + } + +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/BootstrapServiceRegistryProducer.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/BootstrapServiceRegistryProducer.java new file mode 100644 index 000000000000..8556d81ddc87 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/BootstrapServiceRegistryProducer.java @@ -0,0 +1,17 @@ +/* + * 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.testing.orm.junit; + +import org.hibernate.boot.registry.BootstrapServiceRegistry; +import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder; + +/** + * Producer of BootstrapServiceRegistry + */ +public interface BootstrapServiceRegistryProducer { + BootstrapServiceRegistry produceServiceRegistry(BootstrapServiceRegistryBuilder builder); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ClassLoadingIsolaterExtension.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ClassLoadingIsolaterExtension.java new file mode 100644 index 000000000000..e4caab47c772 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ClassLoadingIsolaterExtension.java @@ -0,0 +1,56 @@ +/* + * 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.testing.orm.junit; + +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; + +import org.jboss.logging.Logger; + +public class ClassLoadingIsolaterExtension implements AfterEachCallback, BeforeEachCallback { + + private static final Logger log = Logger.getLogger( ClassLoadingIsolaterExtension.class ); + + private IsolatedClassLoaderProvider provider; + + public interface IsolatedClassLoaderProvider { + ClassLoader buildIsolatedClassLoader(); + + void releaseIsolatedClassLoader(ClassLoader isolatedClassLoader); + } + + + private ClassLoader isolatedClassLoader; + private ClassLoader originalTCCL; + + @Override + public void afterEach(ExtensionContext context) throws Exception { + assert Thread.currentThread().getContextClassLoader() == isolatedClassLoader; + log.infof( "Reverting TCCL [%s] -> [%s]", isolatedClassLoader, originalTCCL ); + + Thread.currentThread().setContextClassLoader( originalTCCL ); + provider.releaseIsolatedClassLoader( isolatedClassLoader ); + } + + @Override + public void beforeEach(ExtensionContext context) throws Exception { + Object testInstance = context.getTestInstance().get(); + if ( !( testInstance instanceof IsolatedClassLoaderProvider ) ) { + throw new RuntimeException( + "Test @ExtendWith( ClassLoadingIsolaterExtension.class ) have to implement ClassLoadingIsolaterExtension.IsolatedClassLoaderProvider" ); + } + provider = (IsolatedClassLoaderProvider) testInstance; + originalTCCL = Thread.currentThread().getContextClassLoader(); + isolatedClassLoader = provider.buildIsolatedClassLoader(); + + log.infof( "Overriding TCCL [%s] -> [%s]", originalTCCL, isolatedClassLoader ); + + Thread.currentThread().setContextClassLoader( isolatedClassLoader ); + + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectContext.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectContext.java new file mode 100644 index 000000000000..2672b89891e7 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectContext.java @@ -0,0 +1,60 @@ +/* + * 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.testing.orm.junit; + +import java.lang.reflect.Constructor; +import java.sql.Connection; +import java.sql.Driver; +import java.util.Properties; + +import org.hibernate.HibernateException; +import org.hibernate.cfg.Environment; +import org.hibernate.dialect.Dialect; +import org.hibernate.internal.util.ReflectHelper; + +/** + * @author Christian Beikov + */ +public final class DialectContext { + + private static Dialect dialect; + + static void init() { + final Properties properties = Environment.getProperties(); + final String dialectName = properties.getProperty( Environment.DIALECT ); + if ( dialectName == null ) { + throw new HibernateException( "The dialect was not set. Set the property hibernate.dialect." ); + } + try { + final Class dialectClass = ReflectHelper.classForName( dialectName ); + final Constructor constructor = dialectClass.getConstructor(); + Driver driver = (Driver) Class.forName( properties.getProperty( Environment.DRIVER ) ).newInstance(); + Properties props = new Properties(); + props.setProperty( "user", properties.getProperty( Environment.USER ) ); + props.setProperty( "password", properties.getProperty( Environment.PASS ) ); + try (Connection connection = driver.connect( properties.getProperty( Environment.URL ), props )) { + dialect = constructor.newInstance(); + } + } + catch (ClassNotFoundException cnfe) { + throw new HibernateException( "Dialect class not found: " + dialectName, cnfe ); + } + catch (Exception e) { + throw new HibernateException( "Could not instantiate given dialect class: " + dialectName, e ); + } + } + + private DialectContext() { + } + + public static synchronized Dialect getDialect() { + if (dialect==null) { + init(); + } + return dialect; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFeatureCheck.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFeatureCheck.java new file mode 100644 index 000000000000..20e961e6c33e --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFeatureCheck.java @@ -0,0 +1,18 @@ +/* + * 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.testing.orm.junit; + +import org.hibernate.dialect.Dialect; + + +/** + * @author Andrea Boriero + */ +@FunctionalInterface +public interface DialectFeatureCheck { + boolean apply(Dialect dialect); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFeatureChecks.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFeatureChecks.java new file mode 100644 index 000000000000..514b6315777e --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFeatureChecks.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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import org.hibernate.dialect.AbstractHANADialect; +import org.hibernate.dialect.CockroachDB192Dialect; +import org.hibernate.dialect.DB2Dialect; +import org.hibernate.dialect.DerbyDialect; +import org.hibernate.dialect.Dialect; +import org.hibernate.dialect.H2Dialect; +import org.hibernate.dialect.HSQLDialect; +import org.hibernate.dialect.MariaDBDialect; +import org.hibernate.dialect.MySQLDialect; +import org.hibernate.dialect.OracleDialect; +import org.hibernate.dialect.PostgreSQL95Dialect; +import org.hibernate.dialect.PostgreSQLDialect; +import org.hibernate.dialect.SQLServerDialect; + +/** + * Container class for different implementation of the {@link DialectFeatureCheck} interface. + * + * @author Hardy Ferentschik + * @author Steve Ebersole + */ +abstract public class DialectFeatureChecks { + public static class SupportsSequences implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsSequences(); + } + } + + public static class SupportsExpectedLobUsagePattern implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsExpectedLobUsagePattern(); + } + } + + /** + * Does the database support nationalized data in any form + */ + public static class SupportsNationalizedData implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsNationalizedTypes(); + } + } + + public static class UsesInputStreamToInsertBlob implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.useInputStreamToInsertBlob(); + } + } + + public static class SupportsIdentityColumns implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.getIdentityColumnSupport().supportsIdentityColumns(); + } + } + + public static class SupportsColumnCheck implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsColumnCheck(); + } + } + + public static class SupportsNoColumnInsert implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsNoColumnsInsert(); + } + } + + public static class SupportsResultSetPositioningOnForwardOnlyCursorCheck implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsResultSetPositionQueryMethodsOnForwardOnlyCursor(); + } + } + + public static class SupportsCascadeDeleteCheck implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsCascadeDelete(); + } + } + + public static class SupportsCircularCascadeDeleteCheck implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsCircularCascadeDeleteConstraints(); + } + } + + public static class SupportsUnboundedLobLocatorMaterializationCheck implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsExpectedLobUsagePattern() && dialect.supportsUnboundedLobLocatorMaterialization(); + } + } + + public static class SupportsSubqueryAsLeftHandSideInPredicate implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsSubselectAsInPredicateLHS(); + } + } + + public static class SupportLimitCheck implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.getLimitHandler().supportsLimit(); + } + } + + public static class SupportLimitAndOffsetCheck implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.getLimitHandler().supportsLimit() && dialect.getLimitHandler().supportsLimitOffset(); + } + } + + public static class SupportsParametersInInsertSelectCheck implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsParametersInInsertSelect(); + } + } + + public static class HasSelfReferentialForeignKeyBugCheck implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.hasSelfReferentialForeignKeyBug(); + } + } + + public static class SupportsRowValueConstructorSyntaxCheck implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect instanceof AbstractHANADialect + || dialect instanceof CockroachDB192Dialect + || dialect instanceof MySQLDialect + || dialect instanceof PostgreSQLDialect; + } + } + + public static class SupportsJdbcDriverProxying implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return !( dialect instanceof DB2Dialect ) && !( dialect instanceof DerbyDialect ); + } + } + + public static class DoesReadCommittedCauseWritersToBlockReadersCheck implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.doesReadCommittedCauseWritersToBlockReaders(); + } + } + + public static class DoesReadCommittedNotCauseWritersToBlockReadersCheck implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return ! dialect.doesReadCommittedCauseWritersToBlockReaders(); + } + } + + public static class DoesRepeatableReadCauseReadersToBlockWritersCheck implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.doesRepeatableReadCauseReadersToBlockWriters(); + } + } + + public static class SupportsExistsInSelectCheck implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsExistsInSelect(); + } + } + + public static class SupportsLobValueChangePropogation implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsLobValueChangePropogation(); + } + } + + public static class SupportsLockTimeouts implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsLockTimeouts(); + } + } + + public static class SupportsSkipLocked implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsSkipLocked(); + } + } + + public static class DoubleQuoteQuoting implements DialectFeatureCheck { + @Override + public boolean apply(Dialect dialect) { + return '\"' == dialect.openQuote() && '\"' == dialect.closeQuote(); + } + } + + public static class SupportSchemaCreation implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.canCreateSchema(); + } + } + + public static class SupportCatalogCreation implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.canCreateCatalog(); + } + } + + public static class DoesNotSupportFollowOnLocking implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return !dialect.useFollowOnLocking(); + } + } + + public static class SupportPartitionBy implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsPartitionBy(); + } + } + + public static class SupportDropConstraints implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.dropConstraints(); + } + } + + public static class SupportsPadWithChar implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return !( dialect instanceof DerbyDialect ); + } + } + + public static class SupportsGroupByRollup implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect instanceof DB2Dialect + || dialect instanceof OracleDialect + || dialect instanceof PostgreSQL95Dialect + || dialect instanceof SQLServerDialect + || dialect instanceof MySQLDialect; + } + } + + public static class SupportsGroupByGroupingSets implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect instanceof DB2Dialect + || dialect instanceof OracleDialect + || dialect instanceof PostgreSQL95Dialect + || dialect instanceof SQLServerDialect; + } + } + + public static class SupportsUnion implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsUnionAll(); + } + } + + public static class SupportsCharCodeConversion implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + // Derby doesn't support the `ASCII` or `CHR` functions + return !( dialect instanceof DerbyDialect ); + } + } + + public static class SupportsReplace implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + // Derby doesn't support the `REPLACE` function + return !( dialect instanceof DerbyDialect ); + } + } + + public static class SupportNoWait implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsNoWait(); + } + } + + public static class DoesRepeatableReadNotCauseReadersToBlockWritersCheck implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return ! dialect.doesRepeatableReadCauseReadersToBlockWriters(); + } + } + + public static class ForceLobAsLastValue implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.forceLobAsLastValue(); + } + } + + public static class SupportsStringAggregation implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect instanceof H2Dialect + || dialect instanceof HSQLDialect + || dialect instanceof MySQLDialect + || dialect instanceof PostgreSQLDialect + || dialect instanceof AbstractHANADialect + || dialect instanceof CockroachDB192Dialect + || dialect instanceof DB2Dialect + || dialect instanceof OracleDialect + || dialect instanceof SQLServerDialect; + } + } + + public static class SupportsInverseDistributionFunctions implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect instanceof AbstractHANADialect + || dialect instanceof CockroachDB192Dialect + || dialect instanceof DB2Dialect + || dialect instanceof OracleDialect + || dialect instanceof SQLServerDialect; + } + } + + public static class SupportsHypotheticalSetFunctions implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect instanceof PostgreSQLDialect + || dialect instanceof AbstractHANADialect + || dialect instanceof CockroachDB192Dialect + || dialect instanceof DB2Dialect + || dialect instanceof OracleDialect + || dialect instanceof SQLServerDialect; + } + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFilterExtension.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFilterExtension.java new file mode 100644 index 000000000000..d2a3bfada4dd --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFilterExtension.java @@ -0,0 +1,122 @@ +/* + * 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.testing.orm.junit; + +import java.util.List; +import java.util.Locale; + +import org.hibernate.dialect.Dialect; + +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.jupiter.api.extension.ExtensionContext; + +import org.jboss.logging.Logger; + +/** + * JUnit 5 extension used to add {@link RequiresDialect} and {@link SkipForDialect} + * handling + * + * @author Steve Ebersole + */ +public class DialectFilterExtension implements ExecutionCondition { + private static final Logger log = Logger.getLogger( DialectFilterExtension.class ); + + @Override + public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { + final Dialect dialect = getDialect( context ); + if ( dialect == null ) { + throw new RuntimeException( "#getDialect returned null" ); + } + + log.debugf( "Checking Dialect [%s] - context = %s", dialect, context.getDisplayName() ); + + final List effectiveRequiresDialects = TestingUtil.findEffectiveRepeatingAnnotation( + context, + RequiresDialect.class, + RequiresDialects.class + ); + + if ( !effectiveRequiresDialects.isEmpty() ) { + StringBuilder requiredDialects = new StringBuilder( ); + + for ( RequiresDialect requiresDialect : effectiveRequiresDialects ) { + requiredDialects.append( requiresDialect.value() ); + requiredDialects.append( " " ); + + if ( ! requiresDialect.value().isInstance( dialect ) ) { + continue; + } + + if ( requiresDialect.matchSubTypes() || requiresDialect.value().equals( dialect.getClass() ) ) { + return evaluateSkipConditions( context, dialect, "Matched @RequiresDialect" ); + } + } + + return ConditionEvaluationResult.disabled( + String.format( + Locale.ROOT, + "Failed @RequiresDialect(dialect=%s) check - found %s]", + requiredDialects, + dialect.getClass().getName() + ) + ); + } + + return evaluateSkipConditions( context, dialect, "Passed all @SkipForDialects" ); + } + + private ConditionEvaluationResult evaluateSkipConditions(ExtensionContext context, Dialect dialect, String enabledResult) { + final List effectiveSkips = TestingUtil.findEffectiveRepeatingAnnotation( + context, + SkipForDialect.class, + SkipForDialectGroup.class + ); + + for ( SkipForDialect effectiveSkipForDialect : effectiveSkips ) { + if ( effectiveSkipForDialect.matchSubTypes() ) { + if ( effectiveSkipForDialect.dialectClass().isInstance( dialect ) ) { + return ConditionEvaluationResult.disabled( "Matched @SkipForDialect" ); + } + } + else { + if ( effectiveSkipForDialect.dialectClass().equals( dialect.getClass() ) ) { + return ConditionEvaluationResult.disabled( "Matched @SkipForDialect" ); + } + } + } + + List effectiveRequiresDialectFeatures = TestingUtil.findEffectiveRepeatingAnnotation( + context, + RequiresDialectFeature.class, + RequiresDialectFeatureGroup.class + ); + + for ( RequiresDialectFeature effectiveRequiresDialectFeature : effectiveRequiresDialectFeatures ) { + try { + final DialectFeatureCheck dialectFeatureCheck = effectiveRequiresDialectFeature.feature() + .newInstance(); + if ( !dialectFeatureCheck.apply( dialect ) ) { + return ConditionEvaluationResult.disabled( + String.format( + Locale.ROOT, + "Failed @RequiresDialectFeature [%s]", + effectiveRequiresDialectFeature.feature() + ) ); + } + } + catch (InstantiationException | IllegalAccessException e) { + throw new RuntimeException( "Unable to instantiate DialectFeatureCheck class", e ); + } + } + return ConditionEvaluationResult.enabled( enabledResult ); + } + + private Dialect getDialect(ExtensionContext context) { + return DialectContext.getDialect(); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModel.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModel.java new file mode 100644 index 000000000000..558c176bbcae --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModel.java @@ -0,0 +1,111 @@ +/* + * 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.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.hibernate.boot.model.TypeContributor; +import org.hibernate.cache.spi.access.AccessType; + +import org.hibernate.testing.orm.domain.DomainModelDescriptor; +import org.hibernate.testing.orm.domain.StandardDomainModel; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExtendWith; + +import javax.persistence.SharedCacheMode; + +/** + * @asciidoc + * + * Used to define the test model ({@link org.hibernate.boot.spi.MetadataImplementor}) + * to be used for testing. + * + * Can be used by itself, along with {@link DomainModelScopeAware}, to test the MetadataImplementor. E.g. + * + * [source, JAVA, indent=0] + * ---- + * @TestDomain ( ... ) + * class MyTest implements TestDomainAware { + * + * @Test + * public void doTheTest() { + * // use the injected MetadataImplementor + * } + * + * private MetadataImplementor model; + * + * @Override + * public void injectTestModelScope(MetadataImplementor model) { + * this.model = model; + * } + * } + * ---- + * + * + * Can optionally be used with {@link ServiceRegistry} to define the ServiceRegistry used to + * build the MetadataImplementor (passed to + * {@link org.hibernate.boot.MetadataSources#MetadataSources(org.hibernate.service.ServiceRegistry)}). + * + * [source, JAVA, indent=0] + * ---- + * @ServiceRegistry ( ... ) + * @TestDomain ( ... ) + * class MyTest implements TestDomainAware { + * + * @Test + * public void doTheTest() { + * // use the injected MetadataImplementor + * } + * + * private MetadataImplementor model; + * + * @Override + * public void injectTestModelScope(MetadataImplementor model) { + * this.model = model; + * } + * } + * ---- + * + * It can also be used in conjunction with {@link SessionFactory} + * + * @see DomainModelScopeAware + * + * @author Steve Ebersole + */ +@Inherited +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) + +@TestInstance( TestInstance.Lifecycle.PER_CLASS ) + +@ExtendWith( FailureExpectedExtension.class ) +@ExtendWith( ServiceRegistryExtension.class ) +@ExtendWith( ServiceRegistryParameterResolver.class ) + +@ExtendWith( DomainModelExtension.class ) +@ExtendWith( DomainModelParameterResolver.class ) +public @interface DomainModel { + StandardDomainModel[] standardModels() default {}; + Class[] modelDescriptorClasses() default {}; + Class[] annotatedClasses() default {}; + String[] annotatedClassNames() default {}; + String[] annotatedPackageNames() default {}; + String[] xmlMappings() default {}; + + SharedCacheMode sharedCacheMode() default SharedCacheMode.ENABLE_SELECTIVE; + + boolean overrideCacheStrategy() default true; + String concurrencyStrategy() default ""; + + AccessType accessType() default AccessType.READ_WRITE; + + Class[] typeContributors() default {}; +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelExtension.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelExtension.java new file mode 100644 index 000000000000..4bb40bb961df --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelExtension.java @@ -0,0 +1,295 @@ +/* + * 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.testing.orm.junit; + +import java.util.Iterator; +import java.util.Locale; +import java.util.Optional; + +import org.hibernate.boot.Metadata; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.internal.MetadataBuilderImpl; +import org.hibernate.boot.model.TypeContributor; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.spi.MetadataImplementor; +import org.hibernate.mapping.Collection; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.Property; +import org.hibernate.mapping.RootClass; +import org.hibernate.mapping.SimpleValue; +import org.hibernate.resource.beans.spi.ManagedBeanRegistry; + +import org.hibernate.testing.orm.domain.DomainModelDescriptor; +import org.hibernate.testing.orm.domain.StandardDomainModel; +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; +import org.junit.jupiter.api.extension.TestInstancePostProcessor; +import org.junit.platform.commons.support.AnnotationSupport; + +/** + * hibernate-testing implementation of a few JUnit5 contracts to support SessionFactory-based testing, + * including argument injection (or see {@link DomainModelScopeAware}) + * + * @see ServiceRegistryScope + * @see DomainModelExtension + * + * @author Steve Ebersole + */ +public class DomainModelExtension + implements TestInstancePostProcessor, AfterAllCallback, TestExecutionExceptionHandler { + + private static final String MODEL_KEY = MetadataImplementor.class.getName(); + + private static ExtensionContext.Store locateExtensionStore(Object testInstance, ExtensionContext context) { + return JUnitHelper.locateExtensionStore( ServiceRegistryExtension.class, context, testInstance ); + } + + public static DomainModelScope findDomainModelScope(Object testInstance, ExtensionContext context) { + final ExtensionContext.Store store = locateExtensionStore( testInstance, context ); + final DomainModelScope existing = (DomainModelScope) store.get( MODEL_KEY ); + if ( existing != null ) { + return existing; + } + + + final ServiceRegistryScope serviceRegistryScope = ServiceRegistryExtension.findServiceRegistryScope( + testInstance, + context + ); + + final DomainModelProducer modelProducer; + + if ( testInstance instanceof DomainModelProducer ) { + modelProducer = (DomainModelProducer) testInstance; + } + else { + modelProducer = serviceRegistry -> { + if ( !context.getElement().isPresent() ) { + throw new RuntimeException( "Unable to determine how to handle given ExtensionContext : " + context.getDisplayName() ); + } + + final Optional domainModelAnnotationWrapper = AnnotationSupport.findAnnotation( + context.getElement().get(), + DomainModel.class + ); + + if ( !domainModelAnnotationWrapper.isPresent() ) { + throw new RuntimeException( "Could not locate @DomainModel annotation : " + context.getDisplayName() ); + } + + final DomainModel domainModelAnnotation = domainModelAnnotationWrapper.get(); + + final MetadataSources metadataSources = new MetadataSources( serviceRegistry ); + final ManagedBeanRegistry managedBeanRegistry = serviceRegistry.getService( ManagedBeanRegistry.class ); + + for ( String annotatedPackageName : domainModelAnnotation.annotatedPackageNames() ) { + metadataSources.addPackage( annotatedPackageName ); + } + + for ( StandardDomainModel standardDomainModel : domainModelAnnotation.standardModels() ) { + standardDomainModel.getDescriptor().applyDomainModel( metadataSources ); + } + + for ( Class modelDescriptorClass : domainModelAnnotation.modelDescriptorClasses() ) { + try { + final DomainModelDescriptor modelDescriptor = modelDescriptorClass.newInstance(); + modelDescriptor.applyDomainModel( metadataSources ); + } + catch (IllegalAccessException | InstantiationException e) { + throw new RuntimeException( "Error instantiating DomainModelDescriptor - " + modelDescriptorClass.getName(), e ); + } + } + + for ( Class annotatedClass : domainModelAnnotation.annotatedClasses() ) { + metadataSources.addAnnotatedClass( annotatedClass ); + } + + for ( String annotatedClassName : domainModelAnnotation.annotatedClassNames() ) { + metadataSources.addAnnotatedClassName( annotatedClassName ); + } + + for ( String xmlMapping : domainModelAnnotation.xmlMappings() ) { + metadataSources.addResource( xmlMapping ); + } + + final MetadataBuilderImpl metadataBuilder = (MetadataBuilderImpl) metadataSources.getMetadataBuilder(); + + for ( Class contributorType : domainModelAnnotation.typeContributors() ) { + final TypeContributor contributor = managedBeanRegistry.getBean( contributorType ).getBeanInstance(); + contributor.contribute( metadataBuilder, serviceRegistry ); + } + + MetadataImplementor metadataImplementor = metadataBuilder.build(); + applyCacheSettings( + metadataImplementor, + domainModelAnnotation.overrideCacheStrategy(), + domainModelAnnotation.concurrencyStrategy() + ); + + return metadataImplementor; + }; + } + + final DomainModelScopeImpl scope = new DomainModelScopeImpl( serviceRegistryScope, modelProducer ); + + if ( testInstance instanceof DomainModelScopeAware ) { + ( (DomainModelScopeAware) testInstance ).injectTestModelScope( scope ); + } + + locateExtensionStore( testInstance, context ).put( MODEL_KEY, scope ); + + return scope; + } + + protected static final void applyCacheSettings(Metadata metadata, boolean overrideCacheStrategy, String cacheConcurrencyStrategy) { + if ( !overrideCacheStrategy ) { + return; + } + + if ( cacheConcurrencyStrategy.equals( "" ) ) { + return; + } + + for ( PersistentClass entityBinding : metadata.getEntityBindings() ) { + if ( entityBinding.isInherited() ) { + continue; + } + + boolean hasLob = false; + + final Iterator props = entityBinding.getPropertyClosureIterator(); + while ( props.hasNext() ) { + final Property prop = (Property) props.next(); + if ( prop.getValue().isSimpleValue() ) { + if ( isLob( (SimpleValue) prop.getValue() ) ) { + hasLob = true; + break; + } + } + } + + if ( !hasLob ) { + ( (RootClass) entityBinding ).setCacheConcurrencyStrategy( cacheConcurrencyStrategy ); + entityBinding.setCached( true ); + } + } + + for ( Collection collectionBinding : metadata.getCollectionBindings() ) { + boolean isLob = false; + + if ( collectionBinding.getElement().isSimpleValue() ) { + isLob = isLob( (SimpleValue) collectionBinding.getElement() ); + } + + if ( !isLob ) { + collectionBinding.setCacheConcurrencyStrategy( cacheConcurrencyStrategy ); + } + } + } + + private static boolean isLob(SimpleValue value) { + final String typeName = value.getTypeName(); + if ( typeName != null ) { + String significantTypeNamePart = typeName.substring( typeName.lastIndexOf( '.' ) + 1 ) + .toLowerCase( Locale.ROOT ); + switch ( significantTypeNamePart ) { + case "blob": + case "blobtype": + case "clob": + case "clobtype": + case "nclob": + case "nclobtype": + return true; + } + } + return false; + } + + @Override + public void postProcessTestInstance(Object testInstance, ExtensionContext context) { + findDomainModelScope( testInstance, context ); + } + + @Override + public void afterAll(ExtensionContext context) { + final ExtensionContext.Store store = locateExtensionStore( context.getRequiredTestInstance(), context ); + final DomainModelScopeImpl scope = (DomainModelScopeImpl) store.remove( MODEL_KEY ); + + if ( scope != null ) { + scope.close(); + } + } + + @Override + public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { + final ExtensionContext.Store store = locateExtensionStore( context.getRequiredTestInstance(), context ); + final DomainModelScopeImpl scope = (DomainModelScopeImpl) store.get( MODEL_KEY ); + + if ( scope != null ) { + scope.releaseModel(); + } + + throw throwable; + } + + public static class DomainModelScopeImpl implements DomainModelScope, ExtensionContext.Store.CloseableResource { + private final ServiceRegistryScope serviceRegistryScope; + private final DomainModelProducer producer; + + private MetadataImplementor model; + private boolean active = true; + + public DomainModelScopeImpl( + ServiceRegistryScope serviceRegistryScope, + DomainModelProducer producer) { + this.serviceRegistryScope = serviceRegistryScope; + this.producer = producer; + + this.model = createDomainModel(); + } + + private MetadataImplementor createDomainModel() { + verifyActive(); + + final StandardServiceRegistry registry = serviceRegistryScope.getRegistry(); + model = producer.produceModel( registry ); + return model; + } + + @Override + public MetadataImplementor getDomainModel() { + verifyActive(); + + if ( model == null ) { + model = createDomainModel(); + } + return model; + } + + private void verifyActive() { + if ( !active ) { + throw new RuntimeException( "DomainModelScope no longer active" ); + } + } + + + @Override + public void close() { + active = false; + releaseModel(); + } + + public void releaseModel() { + model = null; + } + } + + protected void afterMetadataBuilt(Metadata metadata) { + } + +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelFunctionalTesting.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelFunctionalTesting.java new file mode 100644 index 000000000000..9a847b462e96 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelFunctionalTesting.java @@ -0,0 +1,38 @@ +/* + * 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.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * @author Steve Ebersole + */ +@Inherited +@Retention( RetentionPolicy.RUNTIME ) +@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE}) + +@TestInstance( TestInstance.Lifecycle.PER_CLASS ) + +//@ExtendWith( ServiceRegistryExtension.class ) +//@ExtendWith( ServiceRegistryParameterResolver.class ) + +@ServiceRegistryFunctionalTesting + +@ExtendWith( ExpectedExceptionExtension.class ) +@ExtendWith( DialectFilterExtension.class ) + +@ExtendWith( DomainModelExtension.class ) +@ExtendWith( DomainModelParameterResolver.class ) +public @interface DomainModelFunctionalTesting { +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelParameterResolver.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelParameterResolver.java new file mode 100644 index 000000000000..6393ec0ad98b --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelParameterResolver.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.testing.orm.junit; + +import org.hibernate.boot.spi.MetadataImplementor; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ParameterResolver; + +/** + * @author Steve Ebersole + */ +public class DomainModelParameterResolver implements ParameterResolver { + @Override + public boolean supportsParameter( + ParameterContext parameterContext, + ExtensionContext extensionContext) throws ParameterResolutionException { + return JUnitHelper.supportsParameterInjection( parameterContext, MetadataImplementor.class, DomainModelScope.class ); + } + + @Override + public Object resolveParameter( + ParameterContext parameterContext, + ExtensionContext extensionContext) throws ParameterResolutionException { + final DomainModelScope modelScope = DomainModelExtension.findDomainModelScope( + extensionContext.getRequiredTestInstance(), + extensionContext + ); + + final Class parameterType = parameterContext.getParameter().getType(); + + if ( parameterType.isAssignableFrom( DomainModelScope.class ) ) { + return modelScope; + } + + if ( parameterType.isAssignableFrom( MetadataImplementor.class ) ) { + return modelScope.getDomainModel(); + } + + throw new IllegalStateException( "Unsupported parameter type : " + parameterType.getName() ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelProducer.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelProducer.java new file mode 100644 index 000000000000..c85fcdf2bba5 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelProducer.java @@ -0,0 +1,17 @@ +/* + * 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.testing.orm.junit; + +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.spi.MetadataImplementor; + +/** + * @author Steve Ebersole + */ +public interface DomainModelProducer { + MetadataImplementor produceModel(StandardServiceRegistry serviceRegistry); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelScope.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelScope.java new file mode 100644 index 000000000000..34211eddf16e --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelScope.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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import java.util.Locale; +import java.util.function.Consumer; + +import org.hibernate.UnknownEntityTypeException; +import org.hibernate.boot.spi.MetadataImplementor; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.RootClass; + +/** + * @author Steve Ebersole + */ +public interface DomainModelScope { + MetadataImplementor getDomainModel(); + + default void visitHierarchies(Consumer action) { + getDomainModel().getEntityBindings().forEach( + persistentClass -> { + if ( persistentClass instanceof RootClass ) { + action.accept( (RootClass) persistentClass ); + } + } + ); + } + + default void withHierarchy(Class rootType, Consumer action) { + withHierarchy( rootType.getName(), action ); + } + + default void withHierarchy(String rootTypeName, Consumer action) { + final PersistentClass entityBinding = getDomainModel().getEntityBinding( rootTypeName ); + + if ( entityBinding == null ) { + throw new UnknownEntityTypeException( + String.format( + Locale.ROOT, + "Could not resolve `%s` as an entity type", + rootTypeName + ) + ); + } + + action.accept( entityBinding.getRootClass() ); + } + + + // ... +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelScopeAware.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelScopeAware.java new file mode 100644 index 000000000000..393c6fea06cf --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelScopeAware.java @@ -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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +/** + * @author Steve Ebersole + */ +public interface DomainModelScopeAware { + void injectTestModelScope(DomainModelScope modelScope); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/EntityManagerFactoryExtension.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/EntityManagerFactoryExtension.java new file mode 100644 index 000000000000..604561f62dc2 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/EntityManagerFactoryExtension.java @@ -0,0 +1,278 @@ +/* + * 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.testing.orm.junit; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import javax.persistence.spi.PersistenceUnitInfo; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Environment; +import org.hibernate.internal.util.ReflectHelper; +import org.hibernate.jpa.boot.internal.PersistenceUnitInfoDescriptor; +import org.hibernate.jpa.boot.spi.Bootstrap; +import org.hibernate.jpa.boot.spi.EntityManagerFactoryBuilder; +import org.hibernate.tool.schema.Action; + +import org.hibernate.testing.jdbc.SharedDriverManagerConnectionProviderImpl; +import org.hibernate.testing.orm.domain.DomainModelDescriptor; +import org.hibernate.testing.orm.domain.StandardDomainModel; +import org.hibernate.testing.orm.jpa.PersistenceUnitInfoImpl; +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; +import org.junit.jupiter.api.extension.TestInstancePostProcessor; +import org.junit.platform.commons.support.AnnotationSupport; + +import org.jboss.logging.Logger; + +/** + * hibernate-testing implementation of a few JUnit5 contracts to support SessionFactory-based testing, + * including argument injection (or see {@link SessionFactoryScopeAware}) + * + * @author Steve Ebersole + * + * @see DomainModelExtension + * @see SessionFactoryExtension + */ +public class EntityManagerFactoryExtension + implements TestInstancePostProcessor, AfterAllCallback, TestExecutionExceptionHandler { + + private static final Logger log = Logger.getLogger( EntityManagerFactoryExtension.class ); + private static final String EMF_KEY = EntityManagerFactoryScope.class.getName(); + + private static ExtensionContext.Store locateExtensionStore(Object testInstance, ExtensionContext context) { + return JUnitHelper.locateExtensionStore( EntityManagerFactoryExtension.class, context, testInstance ); + } + + public static EntityManagerFactoryScope findEntityManagerFactoryScope( + Object testInstance, + ExtensionContext context) { + final ExtensionContext.Store store = locateExtensionStore( testInstance, context ); + final EntityManagerFactoryScope existing = (EntityManagerFactoryScope) store.get( EMF_KEY ); + if ( existing != null ) { + return existing; + } + + if ( !context.getElement().isPresent() ) { + throw new RuntimeException( "Unable to determine how to handle given ExtensionContext : " + context.getDisplayName() ); + } + + final Optional emfAnnWrapper = AnnotationSupport.findAnnotation( + context.getElement().get(), + Jpa.class + ); + final Jpa emfAnn = emfAnnWrapper.orElseThrow( () -> new RuntimeException( "Could not locate @EntityManagerFactory" ) ); + + final PersistenceUnitInfoImpl pui = new PersistenceUnitInfoImpl( emfAnn.persistenceUnitName() ); + + pui.setTransactionType( emfAnn.transactionType() ); + pui.setCacheMode( emfAnn.sharedCacheMode() ); + pui.setValidationMode( emfAnn.validationMode() ); + pui.setExcludeUnlistedClasses( emfAnn.excludeUnlistedClasses() ); + + // JpaCompliance + pui.getProperties().put( AvailableSettings.JPA_QUERY_COMPLIANCE, emfAnn.queryComplianceEnabled() ); + pui.getProperties().put( AvailableSettings.JPA_TRANSACTION_COMPLIANCE, emfAnn.transactionComplianceEnabled() ); + pui.getProperties().put( AvailableSettings.JPA_CLOSED_COMPLIANCE, emfAnn.closedComplianceEnabled() ); + pui.getProperties().put( AvailableSettings.JPA_PROXY_COMPLIANCE, emfAnn.proxyComplianceEnabled() ); + pui.getProperties().put( AvailableSettings.JPA_CACHING_COMPLIANCE, emfAnn.cacheComplianceEnabled() ); + pui.getProperties().put( AvailableSettings.JPA_ID_GENERATOR_GLOBAL_SCOPE_COMPLIANCE, emfAnn.generatorScopeComplianceEnabled() ); + + final Setting[] properties = emfAnn.properties(); + for ( int i = 0; i < properties.length; i++ ) { + final Setting property = properties[i]; + pui.getProperties().setProperty( property.name(), property.value() ); + } + + pui.getProperties().setProperty( + AvailableSettings.GENERATE_STATISTICS, + Boolean.toString( emfAnn.generateStatistics() ) + ); + + if ( emfAnn.exportSchema() ) { + pui.getProperties().setProperty( + AvailableSettings.HBM2DDL_DATABASE_ACTION, + Action.CREATE_DROP.getExternalHbm2ddlName() + ); + } + + if ( emfAnn.annotatedPackageNames().length > 0 ) { + pui.applyManagedClassNames( emfAnn.annotatedPackageNames() ); + } + + if ( emfAnn.annotatedClassNames().length > 0 ) { + pui.applyManagedClassNames( emfAnn.annotatedClassNames() ); + } + + if ( emfAnn.annotatedClasses().length > 0 ) { + for ( int i = 0; i < emfAnn.annotatedClasses().length; i++ ) { + pui.applyManagedClassNames( emfAnn.annotatedClasses()[i].getName() ); + } + } + + if ( emfAnn.xmlMappings().length > 0 ) { + pui.applyMappingFiles( emfAnn.xmlMappings() ); + } + + if ( emfAnn.standardModels().length > 0 ) { + for ( int i = 0; i < emfAnn.standardModels().length; i++ ) { + final StandardDomainModel standardDomainModel = emfAnn.standardModels()[i]; + for ( int i1 = 0; i1 < standardDomainModel.getDescriptor().getAnnotatedClasses().length; i1++ ) { + final Class annotatedClass = standardDomainModel.getDescriptor().getAnnotatedClasses()[i1]; + pui.applyManagedClassNames( annotatedClass.getName() ); + } + } + } + + if ( emfAnn.modelDescriptorClasses().length > 0 ) { + for ( int i = 0; i < emfAnn.modelDescriptorClasses().length; i++ ) { + final Class modelDescriptorClass = emfAnn.modelDescriptorClasses()[i]; + final DomainModelDescriptor domainModelDescriptor = instantiateDomainModelDescriptor( + modelDescriptorClass ); + for ( int i1 = 0; i1 < domainModelDescriptor.getAnnotatedClasses().length; i1++ ) { + final Class annotatedClass = domainModelDescriptor.getAnnotatedClasses()[i1]; + pui.applyManagedClassNames( annotatedClass.getName() ); + } + } + } + + final Map integrationSettings = new HashMap<>(); + + ( (Map) Environment.getProperties() ).forEach( + (key, value) -> + integrationSettings.put( (String) key, value ) + ); + + if ( !integrationSettings.containsKey( Environment.CONNECTION_PROVIDER ) ) { + integrationSettings.put( + AvailableSettings.CONNECTION_PROVIDER, + SharedDriverManagerConnectionProviderImpl.getInstance() + ); + } + for ( int i = 0; i < emfAnn.integrationSettings().length; i++ ) { + final Setting setting = emfAnn.integrationSettings()[i]; + integrationSettings.put( setting.name(), setting.value() ); + } + + for ( SettingProvider providerAnn : emfAnn.settingProviders() ) { + final Class> providerImpl = providerAnn.provider(); + try { + final SettingProvider.Provider provider = providerImpl.getConstructor().newInstance(); + integrationSettings.put( providerAnn.settingName(), provider.getSetting() ); + } + catch (Exception e) { + log.error( "Error obtaining setting provider for " + providerImpl.getName(), e ); + } + } + + final EntityManagerFactoryScopeImpl scope = new EntityManagerFactoryScopeImpl( pui, integrationSettings ); + + locateExtensionStore( testInstance, context ).put( EMF_KEY, scope ); + + return scope; + } + + private static DomainModelDescriptor instantiateDomainModelDescriptor(Class modelDescriptorClass) { + // first, see if it has a static singleton reference and use that if so + try { + final Field[] declaredFields = modelDescriptorClass.getDeclaredFields(); + for ( int i = 0; i < declaredFields.length; i++ ) { + final Field field = declaredFields[i]; + if ( ReflectHelper.isStaticField( field ) ) { + final Object value = field.get( null ); + if ( value instanceof DomainModelDescriptor ) { + return (DomainModelDescriptor) value; + } + } + } + } + catch (IllegalAccessException e) { + throw new RuntimeException( + "Problem accessing DomainModelDescriptor fields : " + modelDescriptorClass.getName(), + e + ); + } + + // no singleton field, try to instantiate it via reflection + try { + return modelDescriptorClass.getConstructor( null ).newInstance( null ); + } + catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + throw new RuntimeException( + "Problem instantiation DomainModelDescriptor : " + modelDescriptorClass.getName(), + e + ); + } + } + + @Override + public void postProcessTestInstance(Object testInstance, ExtensionContext context) { + log.tracef( "#postProcessTestInstance(%s, %s)", testInstance, context.getDisplayName() ); + + findEntityManagerFactoryScope( testInstance, context ); + } + + @Override + public void afterAll(ExtensionContext context) { + log.tracef( "#afterAll(%s)", context.getDisplayName() ); + + final Object testInstance = context.getRequiredTestInstance(); + + if ( testInstance instanceof SessionFactoryScopeAware ) { + ( (SessionFactoryScopeAware) testInstance ).injectSessionFactoryScope( null ); + } + + final EntityManagerFactoryScopeImpl removed = (EntityManagerFactoryScopeImpl) locateExtensionStore( + testInstance, + context + ).remove( EMF_KEY ); + if ( removed != null ) { + removed.close(); + } + } + + @Override + public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { + log.tracef( "#handleTestExecutionException(%s, %s)", context.getDisplayName(), throwable ); + + try { + final Object testInstance = context.getRequiredTestInstance(); + final ExtensionContext.Store store = locateExtensionStore( testInstance, context ); + final EntityManagerFactoryScopeImpl scope = (EntityManagerFactoryScopeImpl) store.get( EMF_KEY ); + scope.releaseEntityManagerFactory(); + } + catch (Exception ignore) { + } + + throw throwable; + } + + private static class EntityManagerFactoryScopeImpl extends AbstractEntityManagerFactoryScope { + private final PersistenceUnitInfo persistenceUnitInfo; + private final Map integrationSettings; + + private EntityManagerFactoryScopeImpl( + PersistenceUnitInfo persistenceUnitInfo, + Map integrationSettings) { + this.persistenceUnitInfo = persistenceUnitInfo; + this.integrationSettings = integrationSettings; + } + + protected javax.persistence.EntityManagerFactory createEntityManagerFactory() { + final EntityManagerFactoryBuilder emfBuilder = Bootstrap.getEntityManagerFactoryBuilder( + new PersistenceUnitInfoDescriptor( persistenceUnitInfo ), + integrationSettings + ); + + return emfBuilder.build(); + } + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/EntityManagerFactoryParameterResolver.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/EntityManagerFactoryParameterResolver.java new file mode 100644 index 000000000000..a722bf80077c --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/EntityManagerFactoryParameterResolver.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.testing.orm.junit; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ParameterResolver; + +import javax.persistence.EntityManagerFactory; + +import static org.hibernate.testing.orm.junit.EntityManagerFactoryExtension.findEntityManagerFactoryScope; + +/** + * @author Steve Ebersole + */ +public class EntityManagerFactoryParameterResolver implements ParameterResolver { + @Override + public boolean supportsParameter( + ParameterContext parameterContext, + ExtensionContext extensionContext) throws ParameterResolutionException { + return JUnitHelper.supportsParameterInjection( + parameterContext, + EntityManagerFactory.class, + EntityManagerFactoryScope.class + ); + } + + @Override + public Object resolveParameter( + ParameterContext parameterContext, + ExtensionContext extensionContext) throws ParameterResolutionException { + final EntityManagerFactoryScope scope = findEntityManagerFactoryScope( + extensionContext.getRequiredTestInstance(), + extensionContext + ); + + if ( EntityManagerFactoryScope.class.isAssignableFrom( parameterContext.getParameter().getType() ) ) { + return scope; + } + + assert EntityManagerFactory.class.isAssignableFrom( parameterContext.getParameter().getType() ); + return scope.getEntityManagerFactory(); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/EntityManagerFactoryProducer.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/EntityManagerFactoryProducer.java new file mode 100644 index 000000000000..a836d5aa3b49 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/EntityManagerFactoryProducer.java @@ -0,0 +1,29 @@ +/* + * 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.testing.orm.junit; + +import javax.persistence.EntityManagerFactory; + +/** + * Contract for something that can build a SessionFactory. + * + * Used by SessionFactoryScopeExtension to create the + * SessionFactoryScope. + * + * Generally speaking, a test class would implement SessionFactoryScopeContainer + * and return the SessionFactoryProducer to be used for those tests. + * The SessionFactoryProducer is then used to build the SessionFactoryScope + * which is injected back into the SessionFactoryScopeContainer + * + * @see EntityManagerFactoryExtension + * @see EntityManagerFactoryScope + * + * @author Steve Ebersole + */ +public interface EntityManagerFactoryProducer { + EntityManagerFactory produceEntityManagerFactory(); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/EntityManagerFactoryScope.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/EntityManagerFactoryScope.java new file mode 100644 index 000000000000..674e76d0730f --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/EntityManagerFactoryScope.java @@ -0,0 +1,36 @@ +/* + * 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.testing.orm.junit; + +import java.util.function.Consumer; +import java.util.function.Function; + +import org.hibernate.resource.jdbc.spi.StatementInspector; + +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; + +/** + * @author Steve Ebersole + */ +public interface EntityManagerFactoryScope { + EntityManagerFactory getEntityManagerFactory(); + void releaseEntityManagerFactory(); + + StatementInspector getStatementInspector(); + T getStatementInspector(Class type); + + void inEntityManager(Consumer action); + void inTransaction(Consumer action); + void inTransaction(EntityManager entityManager, Consumer action); + + T fromEntityManager(Function action); + T fromTransaction(Function action); + T fromTransaction(EntityManager entityManager, Function action); + + +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/EntityManagerFactoryScopeContainer.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/EntityManagerFactoryScopeContainer.java new file mode 100644 index 000000000000..53aa8397d72f --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/EntityManagerFactoryScopeContainer.java @@ -0,0 +1,28 @@ +/* + * 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.testing.orm.junit; + +/** + * The keystone in EntityManagerFactoryScopeExtension support. + * + * This is how the extensions know how to build an EntityManagerFactory (scope) + * and how to inject that EntityManagerFactory (scope) back into the test. + * + * @author Chris Cranford + */ +public interface EntityManagerFactoryScopeContainer { + /** + * Callback to inject the EntityManagerFactoryScope into the container. + */ + void injectEntityManagerFactoryScope(EntityManagerFactoryScope scope); + + /** + * Obtain the {@link EntityManagerFactoryProducer}. Quite often this is also + * implemented by the container itself. + */ + EntityManagerFactoryProducer getEntityManagerFactoryProducer(); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/EntityManagerFactoryScopeExtension.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/EntityManagerFactoryScopeExtension.java new file mode 100644 index 000000000000..e1eefbdc4b0a --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/EntityManagerFactoryScopeExtension.java @@ -0,0 +1,116 @@ +/* + * 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.testing.orm.junit; + +import java.util.Optional; + +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; +import org.junit.jupiter.api.extension.TestInstancePostProcessor; + +import org.jboss.logging.Logger; + +import javax.persistence.EntityManagerFactory; + +import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.create; + +/** + * The thing that actually manages lifecycle of the EntityManagerFactory related to a test class. + * Work in conjunction with EntityManagerFactoryScope and EntityManagerFactoryScopeContainer. + * + * @see EntityManagerFactoryScope + * @see EntityManagerFactoryScopeContainer + * @see EntityManagerFactoryProducer + * + * @author Chris Cranford + */ +public class EntityManagerFactoryScopeExtension + implements TestInstancePostProcessor, AfterAllCallback, TestExecutionExceptionHandler { + + private static final Logger log = Logger.getLogger( EntityManagerFactoryScopeExtension.class ); + + public static ExtensionContext.Namespace namespace(Object testInstance) { + return create( EntityManagerFactoryScopeExtension.class.getName(), testInstance ); + } + + public static Optional findEntityManagerFactoryScope(ExtensionContext context) { + final Optional entityManagerFactoryScope = Optional.ofNullable( + context.getStore( namespace( context.getRequiredTestInstance() ) ) + .get( ENTITYMANAGER_FACTORY_KEY ) + ); + return entityManagerFactoryScope; + } + + public static final Object ENTITYMANAGER_FACTORY_KEY = "ENTITYMANAGER_FACTORY"; + + public EntityManagerFactoryScopeExtension() { + log.trace( "EntityManagerFactoryScopeExtension#" ); + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // TestInstancePostProcessor + + @Override + public void postProcessTestInstance(Object testInstance, ExtensionContext context) { + log.trace( "EntityManagerFactoryScopeExtension#postProcessTestInstance" ); + if ( EntityManagerFactoryScopeContainer.class.isInstance( testInstance ) ) { + final EntityManagerFactoryScopeContainer scopeContainer = EntityManagerFactoryScopeContainer.class.cast( + testInstance ); + final EntityManagerFactoryScope scope = new EntityManagerFactoryScopeImpl( + scopeContainer.getEntityManagerFactoryProducer() + ); + context.getStore( namespace( testInstance ) ).put( ENTITYMANAGER_FACTORY_KEY, scope ); + + scopeContainer.injectEntityManagerFactoryScope( scope ); + } + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // AfterAllCallback + + @Override + public void afterAll(ExtensionContext context) { + final EntityManagerFactoryScope scope = (EntityManagerFactoryScope) + context.getStore( namespace( context.getRequiredTestInstance() ) ).remove( ENTITYMANAGER_FACTORY_KEY ); + if ( scope != null ) { + scope.releaseEntityManagerFactory(); + } + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // TestExecutionExceptionHandler + + @Override + public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { + final Optional scopeOptional = findEntityManagerFactoryScope( context ); + if ( ! scopeOptional.isPresent() ) { + log.debug( "Could not locate EntityManagerFactoryScope on exception" ); + } + else { + scopeOptional.get().releaseEntityManagerFactory(); + } + + throw throwable; + } + + private static class EntityManagerFactoryScopeImpl extends AbstractEntityManagerFactoryScope { + + private final EntityManagerFactoryProducer producer; + + public EntityManagerFactoryScopeImpl(EntityManagerFactoryProducer producer) { + log.trace( "EntityManagerFactoryScope#" ); + this.producer = producer; + } + + @Override + protected EntityManagerFactory createEntityManagerFactory() { + return producer.produceEntityManagerFactory(); + } + } + +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ExpectedException.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ExpectedException.java new file mode 100644 index 000000000000..302c77edf3d1 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ExpectedException.java @@ -0,0 +1,33 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * Annotation that can be used, in conjunction with {@link ExpectedExceptionExtension}, + * to indicate that a specific test is expected to fail in a particular way + * (throw the specified exception) as its "success condition". + * + * @see ExpectedExceptionExtension + * + * @author Steve Ebersole + */ +@Inherited +@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) + +@ExtendWith( ExpectedExceptionExtension.class ) +public @interface ExpectedException { + Class value(); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ExpectedExceptionExtension.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ExpectedExceptionExtension.java new file mode 100644 index 000000000000..8453660b819a --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ExpectedExceptionExtension.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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; + +import org.jboss.logging.Logger; + +/** + * TestExecutionExceptionHandler used in conjunction with {@link ExpectedException} + * to support annotating tests with a specific exception that indicates a + * success (we are expecting that exception in that tested condition). + * + * @see ExpectedException + * + * @author Steve Ebersole + */ +public class ExpectedExceptionExtension implements TestExecutionExceptionHandler { + private static final Logger log = Logger.getLogger( ExpectedExceptionExtension.class ); + + @Override + public void handleTestExecutionException( + ExtensionContext context, + Throwable throwable) throws Throwable { + final ExpectedException annotation = context.getRequiredTestMethod().getAnnotation( ExpectedException.class ); + if ( annotation != null ) { + if ( annotation.value().isInstance( throwable ) ) { + log.debugf( + "Test [%s] threw exception [%s] which matched @ExpectedException : swallowing exception", + context.getDisplayName(), + throwable + ); + return; + } + } + + throw throwable; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ExtraAssertions.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ExtraAssertions.java new file mode 100644 index 000000000000..b3be71f1c03d --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ExtraAssertions.java @@ -0,0 +1,82 @@ +/* + * 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.testing.orm.junit; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.sql.Types; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.fail; + +/** + * @author Steve Ebersole + */ +public final class ExtraAssertions { + private ExtraAssertions() { + } + + public static void assertClassAssignability(Class expected, Class actual) { + if ( !expected.isAssignableFrom( actual ) ) { + fail( "Expected class [" + expected.getName() + "] was not assignable from actual [" + actual.getName() + "]" ); + } + } + + @SuppressWarnings("unchecked") + public static T assertTyping(Class expectedType, Object value) { + if ( !expectedType.isInstance( value ) ) { + fail( + String.format( + "Expecting value of type [%s], but found [%s]", + expectedType.getName(), + value == null ? "" : value + ) + ); + } + return (T) value; + } + + public static void assertJdbcTypeCode(int expected, int actual) { + if ( expected != actual ) { + final String message = String.format( + "JDBC type codes did not match...\n" + + "Expected: %s (%s)\n" + + "Actual : %s (%s)", + jdbcTypeCodeMap().get( expected ), + expected, + jdbcTypeCodeMap().get( actual ), + actual + ); + fail( message ); + } + } + + private static Map jdbcTypeCodeMap; + + private static synchronized Map jdbcTypeCodeMap() { + if ( jdbcTypeCodeMap == null ) { + jdbcTypeCodeMap = generateJdbcTypeCache(); + } + return jdbcTypeCodeMap; + } + + private static Map generateJdbcTypeCache() { + final Field[] fields = Types.class.getFields(); + Map cache = new HashMap<>( (int) ( fields.length * .75 ) + 1 ); + for ( Field field : fields ) { + if ( Modifier.isStatic( field.getModifiers() ) ) { + try { + cache.put( (Integer) field.get( null ), field.getName() ); + } + catch (Throwable ignore) { + } + } + } + return cache; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/FailureExpected.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/FailureExpected.java new file mode 100644 index 000000000000..419abc7fe094 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/FailureExpected.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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * Marks a test method or class as being expected to fail. + * + * @see FailureExpectedExtension + * + * @author Steve Ebersole + */ +@Inherited +@Target({ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Repeatable( FailureExpectedGroup.class ) + +@ExtendWith( FailureExpectedExtension.class ) +public @interface FailureExpected { + /** + * Setting used to indicate that FailureExpected tests should be run and + * that we should validate they still fail. Note that in this "validation + * mode", a test failure is interpreted as a success which is the main + * difference from JUnit's support. + */ + String VALIDATE_FAILURE_EXPECTED = "hibernate.test.validatefailureexpected"; + + /** + * A reason why the failure is expected + */ + String reason() default ""; + + /** + * The key of a JIRA issue which covers this expected failure. + * @return The jira issue key + */ + String jiraKey() default ""; +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/FailureExpectedExtension.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/FailureExpectedExtension.java new file mode 100644 index 000000000000..064eb077e895 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/FailureExpectedExtension.java @@ -0,0 +1,165 @@ +/* + * 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.testing.orm.junit; + +import java.util.Locale; + +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; + +import org.jboss.logging.Logger; + +/** + * JUnit 5 extension used to support {@link FailureExpected} handling + * + * @author Steve Ebersole + */ +public class FailureExpectedExtension + implements ExecutionCondition, BeforeEachCallback, AfterEachCallback, TestExecutionExceptionHandler { + + private static final Logger log = Logger.getLogger( FailureExpectedExtension.class ); + + private static final String IS_MARKED_STORE_KEY = "IS_MARKED"; + private static final String EXPECTED_FAILURE_STORE_KEY = "EXPECTED_FAILURE"; + + + public static final boolean failureExpectedValidation; + + static { + failureExpectedValidation = Boolean.getBoolean( FailureExpected.VALIDATE_FAILURE_EXPECTED ); + log.debugf( "FailureExpectedExtension#failureExpectedValidation = %s", failureExpectedValidation ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // ExecutionCondition + // - used to disable tests that are an `@ExpectedFailure` when + // failureExpectedValidation == false which is the default. + // + // When failureExpectedValidation == true, the test is allowed to + // run and we validate that the test does in fact fail. + + @Override + public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { + log.tracef( "#evaluateExecutionCondition(%s)", context.getDisplayName() ); + + if ( !context.getElement().isPresent() ) { + throw new RuntimeException( "Unable to determine how to handle given ExtensionContext : " + context.getDisplayName() ); + } + + log.debugf( "Evaluating context - %s [failureExpectedValidation = %s]", context.getDisplayName(), failureExpectedValidation ); + + if ( TestingUtil.hasEffectiveAnnotation( context, FailureExpected.class ) + || TestingUtil.hasEffectiveAnnotation( context, FailureExpectedGroup.class ) ) { + // The test is marked as `FailureExpected`... + if ( failureExpectedValidation ) { + log.debugf( "Executing test marked with `@FailureExpected` for validation" ); + return ConditionEvaluationResult.enabled( "@ExpectedFailure validation" ); + } + else { + return ConditionEvaluationResult.disabled( "Disabled : @ExpectedFailure" ); + } + } + + return ConditionEvaluationResult.enabled( "No @ExpectedFailure" ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // BeforeEachCallback + // - used to determine whether a test is considered as an expected + // failure. If so, + + @Override + public void beforeEach(ExtensionContext context) { + log.tracef( "#beforeEach(%s)", context.getDisplayName() ); + + final boolean markedExpectedFailure = TestingUtil.hasEffectiveAnnotation( context, FailureExpected.class ) + || TestingUtil.hasEffectiveAnnotation( context, FailureExpectedGroup.class ); + + log.debugf( "Checking for @FailureExpected [%s] - %s", context.getDisplayName(), markedExpectedFailure ); + + final ExtensionContext.Namespace namespace = generateNamespace( context ); + context.getStore( namespace ).put( IS_MARKED_STORE_KEY, markedExpectedFailure ); + } + + private ExtensionContext.Namespace generateNamespace(ExtensionContext context) { + return ExtensionContext.Namespace.create( + getClass().getName(), + context.getRequiredTestMethod().getClass(), + context.getRequiredTestMethod().getName() + ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // AfterEachCallback - used to interpret the outcome of the test depending + // on whether it was marked as an `@ExpectedFailure` + + + + @Override + public void afterEach(ExtensionContext context) { + log.tracef( "#afterEach(%s)", context.getDisplayName() ); + + final ExtensionContext.Store store = context.getStore( generateNamespace( context ) ); + + final Boolean isMarked = (Boolean) store.remove( IS_MARKED_STORE_KEY ); + log.debugf( "Post-handling for @FailureExpected [%s] - %s", context.getDisplayName(), isMarked ); + + if ( isMarked == Boolean.TRUE ) { + final Throwable expectedFailure = (Throwable) store.remove( EXPECTED_FAILURE_STORE_KEY ); + log.debugf( " >> Captured exception - %s", expectedFailure ); + + if ( expectedFailure == null ) { + // even though we expected a failure, the test did not fail + throw new ExpectedFailureDidNotFail( context ); + } + } + } + + private static class ExpectedFailureDidNotFail extends RuntimeException { + ExpectedFailureDidNotFail(ExtensionContext context) { + super( + String.format( + Locale.ROOT, + "`%s#%s` was marked as `@ExpectedFailure`, but did not fail", + context.getRequiredTestClass().getName(), + context.getRequiredTestMethod().getName() + ) + ); + } + } + + @Override + public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { + log.tracef( "#handleTestExecutionException(%s, %s)", context.getDisplayName(), throwable.getClass().getName() ); + + final ExtensionContext.Store store = context.getStore( generateNamespace( context ) ); + + final Boolean isMarked = (Boolean) store.get( IS_MARKED_STORE_KEY ); + log.debugf( "Handling test exception [%s]; marked @FailureExcepted = %s", context.getDisplayName(), isMarked ); + + if ( isMarked == Boolean.TRUE ) { + // test is marked as an `@ExpectedFailure`: + + // 1) add the exception to the store + store.put( EXPECTED_FAILURE_STORE_KEY, throwable ); + log.debugf( " >> Stored expected failure - %s", throwable ); + + // 2) eat the failure + return; + } + + // otherwise, re-throw + throw throwable; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/FailureExpectedGroup.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/FailureExpectedGroup.java new file mode 100644 index 000000000000..fd76cfbefa64 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/FailureExpectedGroup.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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * @author Steve Ebersole + */ +@Inherited +@Target({ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) + +@ExtendWith( FailureExpectedExtension.class ) +public @interface FailureExpectedGroup { + FailureExpected[] value(); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/FunctionalEntityManagerFactoryTesting.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/FunctionalEntityManagerFactoryTesting.java new file mode 100644 index 000000000000..8bc9f851505a --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/FunctionalEntityManagerFactoryTesting.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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * Composite annotation for functional tests that require + * a functioning EntityManagerFactory. + * + * @apiNote Logically this should also include + * `@TestInstance( TestInstance.Lifecycle.PER_CLASS )` + * but that annotation is not conveyed (is that the + * right word? its not applied to the thing using this annotation). + * Test classes should apply that themselves. + * + * @see EntityManagerFactoryScopeExtension + * @see DialectFilterExtension + * @see FailureExpectedExtension + * + * @author Chris Cranford + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +@TestInstance(TestInstance.Lifecycle.PER_CLASS ) +@ExtendWith(EntityManagerFactoryScopeExtension.class) +@ExtendWith(DialectFilterExtension.class) +@ExtendWith(FailureExpectedExtension.class) +public @interface FunctionalEntityManagerFactoryTesting { +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/JUnitHelper.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/JUnitHelper.java new file mode 100644 index 000000000000..d2978f3bacd2 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/JUnitHelper.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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import org.junit.jupiter.api.extension.Extension; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; + +import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.create; + +/** + * @author Steve Ebersole + */ +public class JUnitHelper { + public static ExtensionContext.Store locateExtensionStore( + Class extensionClass, + ExtensionContext context, + Object scopeObject) { + return context.getStore( create( extensionClass.getName(), scopeObject ) ); + } + + public static ExtensionContext.Store locateExtensionStore( + ExtensionContext context, + Object... scopeRefs) { + return context.getStore( create( scopeRefs ) ); + } + + private JUnitHelper() { + } + + public static boolean supportsParameterInjection(ParameterContext parameterContext, Class... supportedTypes) { + for ( Class supportedType : supportedTypes ) { + if ( parameterContext.getParameter().getType().isAssignableFrom( supportedType ) ) { + return true; + } + } + + return false; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/JiraKey.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/JiraKey.java new file mode 100644 index 000000000000..01a495a6ae70 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/JiraKey.java @@ -0,0 +1,31 @@ +/* + * 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.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Identifies the JIRA issue associated with a test. Is repeatable, so + * multiple JIRA issues can be indicated. + * + * @see JiraKeyGroup + * + * @author Steve Ebersole + */ +@Retention( RetentionPolicy.RUNTIME ) +@Target({ElementType.TYPE, ElementType.METHOD}) +@Repeatable( JiraKeyGroup.class ) +public @interface JiraKey { + /** + * The key for the referenced Jira issue (e.g., HHH-99999) + */ + String value(); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/JiraKeyGroup.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/JiraKeyGroup.java new file mode 100644 index 000000000000..85106fb8571c --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/JiraKeyGroup.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.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Grouping annotation for `@JiraKey` + * + * @see JiraKey + * + * @author Steve Ebersole + */ +@Retention( RetentionPolicy.RUNTIME ) +@Target({ElementType.TYPE, ElementType.METHOD}) +public @interface JiraKeyGroup { + JiraKey[] value(); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/Jpa.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/Jpa.java new file mode 100644 index 000000000000..4572f9ba53d4 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/Jpa.java @@ -0,0 +1,103 @@ +/* + * 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.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.hibernate.jpa.spi.JpaCompliance; + +import org.hibernate.testing.orm.domain.DomainModelDescriptor; +import org.hibernate.testing.orm.domain.StandardDomainModel; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExtendWith; + +import javax.persistence.SharedCacheMode; +import javax.persistence.ValidationMode; +import javax.persistence.spi.PersistenceUnitTransactionType; + + + +/** + * @author Steve Ebersole + */ +@Inherited +@Target( ElementType.TYPE ) +@Retention( RetentionPolicy.RUNTIME ) + +@TestInstance( TestInstance.Lifecycle.PER_CLASS ) + +@ExtendWith( EntityManagerFactoryExtension.class ) +@ExtendWith( EntityManagerFactoryParameterResolver.class ) + +@ExtendWith( FailureExpectedExtension.class ) +public @interface Jpa { + String persistenceUnitName() default "test-pu"; + + /** + * Used to mimic container integration + */ + Setting[] integrationSettings() default {}; + + // todo : multiple persistence units? + + /** + * Persistence unit properties + */ + Setting[] properties() default {}; + + SettingProvider[] settingProviders() default {}; + + boolean generateStatistics() default false; + boolean exportSchema() default true; + + PersistenceUnitTransactionType transactionType() default PersistenceUnitTransactionType.RESOURCE_LOCAL; + SharedCacheMode sharedCacheMode() default SharedCacheMode.UNSPECIFIED; + ValidationMode validationMode() default ValidationMode.NONE; + + /** + * @see JpaCompliance#isJpaQueryComplianceEnabled() + */ + boolean queryComplianceEnabled() default false; + + /** + * @see JpaCompliance#isJpaTransactionComplianceEnabled() + */ + boolean transactionComplianceEnabled() default false; + + /** + * @see JpaCompliance#isJpaClosedComplianceEnabled() + */ + boolean closedComplianceEnabled() default false; + + /** + * @see JpaCompliance#isJpaProxyComplianceEnabled() + */ + boolean proxyComplianceEnabled() default false; + + /** + * @see JpaCompliance#isJpaCacheComplianceEnabled() + */ + boolean cacheComplianceEnabled() default false; + + /** + * @see JpaCompliance#isGlobalGeneratorScopeEnabled() + */ + boolean generatorScopeComplianceEnabled() default false; + + boolean excludeUnlistedClasses() default false; + + StandardDomainModel[] standardModels() default {}; + Class[] modelDescriptorClasses() default {}; + Class[] annotatedClasses() default {}; + String[] annotatedClassNames() default {}; + String[] annotatedPackageNames() default {}; + String[] xmlMappings() default {}; +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/Logger.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/Logger.java new file mode 100644 index 000000000000..fa8c714bed3e --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/Logger.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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +/** + * Must specify one of {@link #loggerNameClass} or {@link #loggerName()} + */ +public @interface Logger { + // I think we can actually look up the "bare" Logger and still get the same + // capability in terms of register listeners + //Class messageLoggerClass() default CoreMessageLogger.class; + + /** + * The `Class` used as the base for the logger name. + * + * @see org.jboss.logging.Logger#getLogger(Class) + */ + Class loggerNameClass() default void.class; + + /** + * The `Class` used as the base for the logger name. + * + * @see org.jboss.logging.Logger#getLogger(Class) + */ + String loggerName() default ""; +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/LoggingInspections.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/LoggingInspections.java new file mode 100644 index 000000000000..1309131df58a --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/LoggingInspections.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.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * Injects the ability to watch multiple for log messages being triggered. + * + * Only available at the class-level + * + * For watching a single message-key, {@link MessageKeyInspection} is a + * better option. + */ +@Inherited +@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) + +@ExtendWith( LoggingInspectionsExtension.class ) +@ExtendWith( LoggingInspectionsScopeResolver.class ) +public @interface LoggingInspections { + Message[] messages() default {}; + + @interface Message { + /** + * The message-key to watch for. The message-key is the combination of + * {@link org.jboss.logging.annotations.MessageLogger#projectCode()} + * and {@link org.jboss.logging.annotations.Message#id()} used by + * JBoss Logging to prefix each messaged log event + */ + String messageKey(); + + /** + * Descriptor of the log messages to watch for + */ + Logger[] loggers() default {}; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/LoggingInspectionsExtension.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/LoggingInspectionsExtension.java new file mode 100644 index 000000000000..711ed7c6982f --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/LoggingInspectionsExtension.java @@ -0,0 +1,60 @@ +/* + * 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.testing.orm.junit; + +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Store; +import org.junit.jupiter.api.extension.TestInstancePostProcessor; + +/** + * @author Steve Ebersole + */ +public class LoggingInspectionsExtension implements TestInstancePostProcessor, BeforeEachCallback { + private static final String KEY = LoggingInspectionsExtension.class.getName(); + + // todo (6.0) : have this implement `AfterEachCallback` support to reset after each test? + + @Override + public void postProcessTestInstance( + Object testInstance, + ExtensionContext context) { + resolveLoggingInspectionScope( testInstance, context ); + } + + @Override + public void beforeEach(ExtensionContext context) { + final Store extensionStore = locateExtensionStore( context.getRequiredTestInstance(), context ); + final LoggingInspectionsScope existing = (LoggingInspectionsScope) extensionStore.get( KEY ); + if ( existing != null ) { + existing.resetWatchers(); + } + } + + public static LoggingInspectionsScope resolveLoggingInspectionScope(Object testInstance, ExtensionContext context) { + final Store extensionStore = locateExtensionStore( testInstance, context ); + final Object existing = extensionStore.get( KEY ); + if ( existing != null ) { + return (LoggingInspectionsScope) existing; + } + + // we'll need to create it... + + // find the annotation + final LoggingInspections loggingInspections = testInstance.getClass().getAnnotation( LoggingInspections.class ); + + // Create the scope and add to context store + final LoggingInspectionsScope scope = new LoggingInspectionsScope( loggingInspections, context ); + extensionStore.put( KEY, scope ); + + return scope; + } + + private static Store locateExtensionStore(Object testInstance, ExtensionContext context) { + return JUnitHelper.locateExtensionStore( EntityManagerFactoryExtension.class, context, testInstance ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/LoggingInspectionsScope.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/LoggingInspectionsScope.java new file mode 100644 index 000000000000..3ab2390754fb --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/LoggingInspectionsScope.java @@ -0,0 +1,73 @@ +/* + * 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.testing.orm.junit; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.extension.ExtensionContext; + +/** + * Manages all of the MessageKeyWatcher defined by LoggingInspectionsScope + */ +public class LoggingInspectionsScope { + private final Map> watcherMap = new HashMap<>(); + + + public LoggingInspectionsScope(LoggingInspections loggingInspections, ExtensionContext context) { + for ( int i = 0; i < loggingInspections.messages().length; i++ ) { + final LoggingInspections.Message message = loggingInspections.messages()[ i ]; + + final String messageKey = message.messageKey().trim(); + assert ! messageKey.isEmpty(); + + if ( message.loggers().length == 0 ) { + return; + } + + final Map messageKeyWatcherMap; + final Map existingMessageKeyWatcherMap = watcherMap.get( messageKey ); + if ( existingMessageKeyWatcherMap != null ) { + messageKeyWatcherMap = existingMessageKeyWatcherMap; + } + else { + messageKeyWatcherMap = new HashMap<>(); + watcherMap.put( messageKey, messageKeyWatcherMap ); + } + + for ( Logger logger : message.loggers() ) { + final String loggerKey = MessageKeyWatcherImpl.loggerKey( logger ); + final MessageKeyWatcherImpl watcher; + final MessageKeyWatcherImpl existingWatcher = messageKeyWatcherMap.get( loggerKey ); + if ( existingWatcher != null ) { + watcher = existingWatcher; + } + else { + watcher = new MessageKeyWatcherImpl( messageKey ); + messageKeyWatcherMap.put( loggerKey, watcher ); + } + watcher.addLogger( logger ); + } + } + } + + public void resetWatchers() { + watcherMap.forEach( + (messageKey,loggerMap) -> loggerMap.forEach( (logger,watcher) -> watcher.reset() ) + ); + } + + public MessageKeyWatcher getWatcher(String messageKey, String loggerName) { + final Map messageKeyWatcherMap = watcherMap.get( messageKey ); + return messageKeyWatcherMap.get( loggerName ); + } + + public MessageKeyWatcher getWatcher(String messageKey, Class loggerNameClass) { + final Map messageKeyWatcherMap = watcherMap.get( messageKey ); + return messageKeyWatcherMap.get( loggerNameClass.getName() ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/LoggingInspectionsScopeResolver.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/LoggingInspectionsScopeResolver.java new file mode 100644 index 000000000000..f7a473e91567 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/LoggingInspectionsScopeResolver.java @@ -0,0 +1,37 @@ +/* + * 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.testing.orm.junit; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ParameterResolver; + +/** + * ParameterResolver implementation for resolving + * {@link LoggingInspectionsScope} ParameterResolver + */ +public class LoggingInspectionsScopeResolver implements ParameterResolver { + @Override + public boolean supportsParameter( + ParameterContext parameterContext, + ExtensionContext extensionContext) { + return LoggingInspectionsScope.class.isAssignableFrom( + parameterContext.getParameter().getType() + ); + } + + @Override + public LoggingInspectionsScope resolveParameter( + ParameterContext parameterContext, + ExtensionContext extensionContext) throws ParameterResolutionException { + return LoggingInspectionsExtension.resolveLoggingInspectionScope( + extensionContext.getRequiredTestInstance(), + extensionContext + ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/MessageKeyInspection.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/MessageKeyInspection.java new file mode 100644 index 000000000000..843b155eebd5 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/MessageKeyInspection.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.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * Injects the ability to watch for a log messages being triggered. + * + * For watching a multiple message-keys, see {@link LoggingInspections} + */ +@Inherited +@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) + +@ExtendWith( MessageKeyInspectionExtension.class ) +@ExtendWith( MessageKeyWatcherResolver.class ) +public @interface MessageKeyInspection { + /** + * The message key to look for. + * + * @apiNote This is effectively a starts-with check. We simply check + * that the logged message starts with the value from here + */ + String messageKey(); + + /** + * The logger to watch on + */ + Logger logger(); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/MessageKeyInspectionExtension.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/MessageKeyInspectionExtension.java new file mode 100644 index 000000000000..1ae6aade4877 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/MessageKeyInspectionExtension.java @@ -0,0 +1,97 @@ +/* + * 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.testing.orm.junit; + +import java.lang.reflect.Method; + +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestInstancePostProcessor; + +import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.create; + +/** + * @author Steve Ebersole + */ +public class MessageKeyInspectionExtension implements TestInstancePostProcessor, BeforeEachCallback { + public static final String KEY = LoggingInspectionsExtension.class.getName(); + + @Override + public void postProcessTestInstance(Object testInstance, ExtensionContext context) { + // Process the MessageKeyInspection annotation that happens at the class-level + + final ExtensionContext.Store instanceStore = resolveInstanceStore( testInstance, context ); + + final Object existing = instanceStore.get( KEY ); + if ( existing != null ) { + // odd, but there would be nothing to do + return; + } + + // find the annotation, create the watcher and add it to the context + final MessageKeyInspection inspection = testInstance.getClass().getAnnotation( MessageKeyInspection.class ); + final MessageKeyWatcherImpl watcher = new MessageKeyWatcherImpl( inspection.messageKey() ); + watcher.addLogger( inspection.logger() ); + + instanceStore.put( KEY, watcher ); + } + + public static ExtensionContext.Store resolveInstanceStore(Object testInstance, ExtensionContext context) { + final ExtensionContext.Namespace instanceStoreNamespace = create( testInstance ); + return context.getStore( instanceStoreNamespace ); + } + + @Override + public void beforeEach(ExtensionContext context) { + final Method method = context.getRequiredTestMethod(); + + final ExtensionContext.Store methodStore = resolveMethodStore( context ); + final MessageKeyWatcher existing = (MessageKeyWatcher) methodStore.get( KEY ); + if ( existing != null ) { + prepareForUse( existing ); + // already there - nothing to do + return; + } + + // if the test-method is annotated, use a one-off watcher for that message + final MessageKeyInspection inspectionAnn = method.getAnnotation( MessageKeyInspection.class ); + if ( inspectionAnn != null ) { + final MessageKeyWatcherImpl watcher = new MessageKeyWatcherImpl( inspectionAnn.messageKey() ); + watcher.addLogger( inspectionAnn.logger() ); + methodStore.put( KEY, watcher ); + prepareForUse( watcher ); + return; + } + + // look for a class/instance-level watcher + final ExtensionContext.Store instanceStore = resolveInstanceStore( context.getRequiredTestInstance(), context ); + final MessageKeyWatcher instanceLevelWatcher = (MessageKeyWatcher) instanceStore.get( KEY ); + if ( instanceLevelWatcher != null ) { + methodStore.put( KEY, instanceLevelWatcher ); + prepareForUse( instanceLevelWatcher ); + } + } + + private void prepareForUse(MessageKeyWatcher watcher) { + watcher.reset(); + } + + public static ExtensionContext.Store resolveMethodStore(ExtensionContext context) { + final ExtensionContext.Namespace instanceStoreNamespace = create( context.getRequiredTestMethod() ); + return context.getStore( instanceStoreNamespace ); + } + + public static MessageKeyWatcher getWatcher(ExtensionContext context) { + final ExtensionContext.Store methodStore = resolveMethodStore( context ); + final Object ref = methodStore.get( KEY ); + if ( ref == null ) { + throw new IllegalStateException( "No watcher available" ); + } + return (MessageKeyWatcher) ref; + } + +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/MessageKeyWatcher.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/MessageKeyWatcher.java new file mode 100644 index 000000000000..68e08b62aa0d --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/MessageKeyWatcher.java @@ -0,0 +1,24 @@ +/* + * 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.testing.orm.junit; + +import java.util.List; + +/** + * @author Steve Ebersole + */ +public interface MessageKeyWatcher { + String getMessageKey(); + + boolean wasTriggered(); + + List getTriggeredMessages(); + + String getFirstTriggeredMessage(); + + void reset(); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/MessageKeyWatcherImpl.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/MessageKeyWatcherImpl.java new file mode 100644 index 000000000000..a5e4f12c41d7 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/MessageKeyWatcherImpl.java @@ -0,0 +1,114 @@ +/* + * 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.testing.orm.junit; + +import java.util.ArrayList; +import java.util.List; + +import org.hibernate.testing.logger.LogInspectionHelper; +import org.hibernate.testing.logger.LogListener; + +import org.jboss.logging.Logger; + +/** + * MessageIdWatcher implementation + */ +public class MessageKeyWatcherImpl implements MessageKeyWatcher, LogListener { + private final String messageKey; + private final List loggerNames = new ArrayList<>(); + private final List triggeredMessages = new ArrayList<>(); + + public MessageKeyWatcherImpl(String messageKey) { + this.messageKey = messageKey; + } + + public void addLoggerName(String name) { + loggerNames.add( name ); + } + + public void addLogger(org.hibernate.testing.orm.junit.Logger loggerAnn) { + final Logger logger; + if ( loggerAnn.loggerNameClass() != void.class ) { + logger = Logger.getLogger( loggerAnn.loggerNameClass() ); + } + else if ( ! "".equals( loggerAnn.loggerName().trim() ) ) { + logger = Logger.getLogger( loggerAnn.loggerName().trim() ); + } + else { + throw new IllegalStateException( + "@LoggingInspections for prefix '" + messageKey + + "' did not specify proper Logger name. Use `@LoggingInspections#loggerName" + + " or `@LoggingInspections#loggerNameClass`" + ); + } + + LogInspectionHelper.registerListener( this, logger ); + } + + public static String loggerKey(org.hibernate.testing.orm.junit.Logger loggerAnn) { + final Logger logger; + if ( loggerAnn.loggerNameClass() != void.class ) { + logger = Logger.getLogger( loggerAnn.loggerNameClass() ); + } + else if ( ! "".equals( loggerAnn.loggerName().trim() ) ) { + logger = Logger.getLogger( loggerAnn.loggerName().trim() ); + } + else { + throw new IllegalArgumentException( + "`@Logger` must specify either `#loggerNameClass` or `#loggerName`" + ); + } + + return logger.getName(); + } + + public List getLoggerNames() { + return loggerNames; + } + + @Override + public String getMessageKey() { + return messageKey; + } + + @Override + public boolean wasTriggered() { + return ! triggeredMessages.isEmpty(); + } + + @Override + public List getTriggeredMessages() { + return triggeredMessages; + } + + @Override + public String getFirstTriggeredMessage() { + return triggeredMessages.isEmpty() ? null : triggeredMessages.get( 0 ); + } + + @Override + public void reset() { + triggeredMessages.clear(); + } + + @Override + public void loggedEvent(Logger.Level level, String renderedMessage, Throwable thrown) { + if ( renderedMessage != null ) { + if ( renderedMessage.startsWith( messageKey ) ) { + triggeredMessages.add( renderedMessage ); + } + } + } + + @Override + public String toString() { + return "MessageIdWatcherImpl{" + + "messageKey='" + messageKey + '\'' + + ", loggerNames=" + loggerNames + + '}'; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/MessageKeyWatcherResolver.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/MessageKeyWatcherResolver.java new file mode 100644 index 000000000000..3c07332e268d --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/MessageKeyWatcherResolver.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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ParameterResolver; + +/** + * @author Steve Ebersole + */ +public class MessageKeyWatcherResolver implements ParameterResolver { + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { + return MessageKeyWatcher.class.isAssignableFrom( parameterContext.getParameter().getType() ); + } + + @Override + public MessageKeyWatcher resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { + return MessageKeyInspectionExtension.getWatcher( extensionContext ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/RequiresDialect.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/RequiresDialect.java new file mode 100644 index 000000000000..bef07cf121ae --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/RequiresDialect.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.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.hibernate.dialect.Dialect; + +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * Indicates that the annotated test class/method should only + * be run when the indicated Dialect is being used. + * + * @author Steve Ebersole + */ +@Inherited +@Retention( RetentionPolicy.RUNTIME ) +@Target({ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +@Repeatable( RequiresDialects.class ) + +@ExtendWith( DialectFilterExtension.class ) +public @interface RequiresDialect { + /** + * The Dialect class to match. + */ + Class value(); + + /** + * Should subtypes of {@link #value()} be matched? + */ + boolean matchSubTypes() default true; + + /** + * Comment describing the reason why the dialect is required. + * + * @return The comment + */ + String comment() default ""; +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/RequiresDialectFeature.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/RequiresDialectFeature.java new file mode 100644 index 000000000000..dbb5dab02ece --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/RequiresDialectFeature.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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * Annotation used to indicate that a test should be run only when the current dialect supports the + * specified feature. + * + * @author Andrea Boriero + */ +@Inherited +@Retention( RetentionPolicy.RUNTIME ) +@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +@Repeatable( RequiresDialectFeatureGroup.class ) + +@ExtendWith( DialectFilterExtension.class ) +public @interface RequiresDialectFeature { + /** + * @return Class which checks the necessary dialect feature + */ + Class feature(); + + /** + * @return Whether the decision of {@link #feature()} is reversed + */ + boolean reverse() default false; + + /** + * Comment describing the reason why the feature is required. + * + * @return The comment + */ + String comment() default ""; + + /** + * The key of a JIRA issue which relates this this feature requirement. + * + * @return The jira issue key + */ + String jiraKey() default ""; +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/RequiresDialectFeatureGroup.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/RequiresDialectFeatureGroup.java new file mode 100644 index 000000000000..425be3e67737 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/RequiresDialectFeatureGroup.java @@ -0,0 +1,37 @@ +/* + * 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.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * Annotation used to indicate that a test should be run only when the current dialect supports the + * specified feature. + * + * @author Hardy Ferentschik + */ +@Inherited +@Retention( RetentionPolicy.RUNTIME ) +@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE}) + +@ExtendWith( DialectFilterExtension.class ) +public @interface RequiresDialectFeatureGroup { + RequiresDialectFeature[] value(); + + /** + * The key of a JIRA issue which relates this this feature requirement. + * + * @return The jira issue key + */ + String jiraKey() default ""; +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/RequiresDialects.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/RequiresDialects.java new file mode 100644 index 000000000000..263c505bfd3c --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/RequiresDialects.java @@ -0,0 +1,28 @@ +/* + * 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.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * @author Andrea Boriero + */ +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE }) + +@ExtendWith( DialectFilterExtension.class ) +public @interface RequiresDialects { + RequiresDialect[] value(); +} + diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistry.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistry.java new file mode 100644 index 000000000000..dc01a9b2350c --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistry.java @@ -0,0 +1,98 @@ +/* + * 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.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.hibernate.boot.registry.StandardServiceInitiator; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.service.spi.ServiceContributor; + +/** + * @asciidoc + * + * Used to define the ServiceRegistry to be used for testing. Can be used alone: + * + * [source, JAVA, indent=0] + * ---- + * @ServiceRegistry ( ... ) + * class MyTest extends ServiceRegistryAware { + * @Test + * public void doTheTest() { + * // use the injected registry... + * + * ... + * } + * + * private StandardServiceRegistry registry; + * + * @Override + * public void injectServiceRegistryScope(StandardServiceRegistry registry) { + * this.registry = registry; + * } + * } + * ---- + * + * It can also be used as the basis for building a + * {@link org.hibernate.boot.spi.MetadataImplementor} via {@link DomainModel} + * or {@link SessionFactoryImplementor} via {@link SessionFactory}, + * with or without {@link ServiceRegistryScopeAware}. E.g. + * + * [source, JAVA, indent=0] + * ---- + * @ServiceRegistry ( ... ) + * @TestDomain ( ... ) + * class MyTest ... { + * } + * ---- + * + * Here, the managed ServiceRegistry is used to create the + * {@link org.hibernate.boot.spi.MetadataImplementor} + * + * @see ServiceRegistryScopeAware + * + * @author Steve Ebersole + */ +@Inherited +@Target( ElementType.TYPE ) +@Retention( RetentionPolicy.RUNTIME ) + +@ServiceRegistryFunctionalTesting +public @interface ServiceRegistry { + Class[] serviceContributors() default {}; + + Class[] initiators() default {}; + + Service[] services() default {}; + JavaService[] javaServices() default {}; + + Setting[] settings() default {}; + + SettingProvider[] settingProviders() default {}; + + /** + * A Hibernate Service registration + */ + @interface Service { + Class role(); + Class impl(); + } + + /** + * A Java service loadable via {@link java.util.ServiceLoader} + */ + @interface JavaService { + Class role(); + Class[] impls(); + } + + +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryExtension.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryExtension.java new file mode 100644 index 000000000000..af64ea459416 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryExtension.java @@ -0,0 +1,336 @@ +/* + * 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.testing.orm.junit; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder; +import org.hibernate.boot.registry.StandardServiceInitiator; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.integrator.spi.Integrator; +import org.hibernate.service.spi.ServiceContributor; + +import org.hibernate.testing.boot.ExtraJavaServicesClassLoaderService; +import org.hibernate.testing.boot.ExtraJavaServicesClassLoaderService.JavaServiceDescriptor; +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; +import org.junit.jupiter.api.extension.TestInstancePostProcessor; +import org.junit.platform.commons.support.AnnotationSupport; + +import org.jboss.logging.Logger; + +/** + * JUnit extension used to manage the StandardServiceRegistry used by a test including + * creating the StandardServiceRegistry and releasing it afterwards + * + * @author Steve Ebersole + */ +public class ServiceRegistryExtension + implements TestInstancePostProcessor, AfterAllCallback, TestExecutionExceptionHandler { + private static final Logger log = Logger.getLogger( ServiceRegistryExtension.class ); + private static final String REGISTRY_KEY = ServiceRegistryScope.class.getName(); + + public static StandardServiceRegistry findServiceRegistry( + Object testInstance, + ExtensionContext context) { + return findServiceRegistryScope( testInstance, context ).getRegistry(); + } + + private static ExtensionContext.Store locateExtensionStore( + Object testInstance, + ExtensionContext context) { + return JUnitHelper.locateExtensionStore( ServiceRegistryExtension.class, context, testInstance ); + } + + public static ServiceRegistryScope findServiceRegistryScope(Object testInstance, ExtensionContext context) { + log.tracef( "#findServiceRegistryScope(%s, %s)", testInstance, context.getDisplayName() ); + + final ExtensionContext.Store store = locateExtensionStore( testInstance, context ); + + ServiceRegistryScopeImpl existingScope = (ServiceRegistryScopeImpl) store.get( REGISTRY_KEY ); + + if ( existingScope == null ) { + log.debugf( "Creating ServiceRegistryScope - %s", context.getDisplayName() ); + + final BootstrapServiceRegistryProducer bsrProducer; + + final Optional bsrAnnWrapper = AnnotationSupport.findAnnotation( + context.getElement().get(), + BootstrapServiceRegistry.class + ); + + if ( bsrAnnWrapper.isPresent() ) { + bsrProducer = bsrBuilder -> { + final BootstrapServiceRegistry bsrAnn = bsrAnnWrapper.get(); + configureJavaServices( bsrAnn, bsrBuilder ); + configureIntegrators( bsrAnn, bsrBuilder ); + + return bsrBuilder.enableAutoClose().build(); + }; + } + else { + bsrProducer = BootstrapServiceRegistryBuilder::build; + } + + final ServiceRegistryProducer ssrProducer; + + if ( testInstance instanceof ServiceRegistryProducer ) { + ssrProducer = (ServiceRegistryProducer) testInstance; + } + else { + ssrProducer = new ServiceRegistryProducerImpl(context); + } + + final ServiceRegistryScopeImpl scope = new ServiceRegistryScopeImpl( bsrProducer, ssrProducer ); + scope.getRegistry(); + + locateExtensionStore( testInstance, context ).put( REGISTRY_KEY, scope ); + + if ( testInstance instanceof ServiceRegistryScopeAware ) { + ( (ServiceRegistryScopeAware) testInstance ).injectServiceRegistryScope( scope ); + } + return scope; + } + + return existingScope; + } + + private static class ServiceRegistryProducerImpl implements ServiceRegistryProducer{ + private final ExtensionContext context; + public ServiceRegistryProducerImpl(ExtensionContext context) { + this.context = context; + if ( !context.getElement().isPresent() ) { + throw new RuntimeException( "Unable to determine how to handle given ExtensionContext : " + context.getDisplayName() ); + } + } + + @Override + public StandardServiceRegistry produceServiceRegistry(StandardServiceRegistryBuilder ssrb) { + // set some baseline test settings + ssrb.applySetting( AvailableSettings.STATEMENT_INSPECTOR, org.hibernate.testing.jdbc.SQLStatementInspector.class ); + + final Optional ssrAnnWrapper = AnnotationSupport.findAnnotation( + context.getElement().get(), + ServiceRegistry.class + ); + + if ( ssrAnnWrapper.isPresent() ) { + final ServiceRegistry serviceRegistryAnn = ssrAnnWrapper.get(); + configureServices( serviceRegistryAnn, ssrb ); + } + + return ssrb.build(); + } + + @Override + public void prepareBootstrapRegistryBuilder(BootstrapServiceRegistryBuilder bsrb) { + + } + } + + private static void configureIntegrators( + BootstrapServiceRegistry bsrAnn, + final BootstrapServiceRegistryBuilder bsrBuilder) { + final Class[] integrators = bsrAnn.integrators(); + if ( integrators.length == 0 ) { + return; + } + + for ( Class integratorImpl : integrators ) { + assert integratorImpl != null; + + try { + final Constructor constructor = integratorImpl.getDeclaredConstructor(); + + final Integrator integrator = constructor.newInstance(); + bsrBuilder.applyIntegrator( integrator ); + } + catch (NoSuchMethodException e) { + throw new IllegalArgumentException( "Could not find no-arg constructor for Integrator : " + integratorImpl.getName(), e ); + } + catch (IllegalAccessException e) { + throw new IllegalArgumentException( "Unable to access no-arg constructor for Integrator : " + integratorImpl.getName(), e ); + } + catch (InstantiationException | InvocationTargetException e) { + throw new IllegalArgumentException( "Unable to instantiate Integrator : " + integratorImpl.getName(), e ); + } + } + } + + private static void configureJavaServices(BootstrapServiceRegistry bsrAnn, BootstrapServiceRegistryBuilder bsrBuilder) { + final BootstrapServiceRegistry.JavaService[] javaServiceAnns = bsrAnn.javaServices(); + if ( javaServiceAnns.length == 0 ) { + return; + } + + final List> javaServiceDescriptors = new ArrayList<>( javaServiceAnns.length ); + for ( int i = 0; i < javaServiceAnns.length; i++ ) { + final BootstrapServiceRegistry.JavaService javaServiceAnn = javaServiceAnns[ i ]; + javaServiceDescriptors.add( + new JavaServiceDescriptor( + javaServiceAnn.role(), + javaServiceAnn.impl() + ) + ); + } + final ExtraJavaServicesClassLoaderService cls = new ExtraJavaServicesClassLoaderService( javaServiceDescriptors ); + bsrBuilder.applyClassLoaderService( cls ); + } + + private static void configureServices(ServiceRegistry serviceRegistryAnn, StandardServiceRegistryBuilder ssrb) { + try { + for ( Setting setting : serviceRegistryAnn.settings() ) { + ssrb.applySetting( setting.name(), setting.value() ); + } + + for ( SettingProvider providerAnn : serviceRegistryAnn.settingProviders() ) { + final Class providerImpl = providerAnn.provider(); + final SettingProvider.Provider provider = providerImpl.getConstructor().newInstance(); + ssrb.applySetting( providerAnn.settingName(), provider.getSetting() ); + } + + for ( Class contributorClass : serviceRegistryAnn.serviceContributors() ) { + final ServiceContributor serviceContributor = contributorClass.newInstance(); + serviceContributor.contribute( ssrb ); + } + + for ( Class initiatorClass : serviceRegistryAnn.initiators() ) { + ssrb.addInitiator( initiatorClass.newInstance() ); + } + + for ( ServiceRegistry.Service service : serviceRegistryAnn.services() ) { + ssrb.addService( (Class) service.role(), service.impl().newInstance() ); + } + } + catch (Exception e) { + throw new RuntimeException( "Could not configure StandardServiceRegistryBuilder", e ); + } + } + + @Override + public void postProcessTestInstance(Object testInstance, ExtensionContext context) { + log.tracef( "#postProcessTestInstance(%s, %s)", testInstance, context.getDisplayName() ); + findServiceRegistryScope( testInstance, context ); + } + + @Override + public void afterAll(ExtensionContext context) { + log.tracef( "#afterAll(%s)", context.getDisplayName() ); + + final Object testInstance = context.getRequiredTestInstance(); + + if ( testInstance instanceof ServiceRegistryScopeAware ) { + ( (ServiceRegistryScopeAware) testInstance ).injectServiceRegistryScope( null ); + } + + final ExtensionContext.Store store = locateExtensionStore( testInstance, context ); + final ServiceRegistryScopeImpl scope = (ServiceRegistryScopeImpl) store.remove( REGISTRY_KEY ); + if ( scope != null ) { + scope.close(); + } + } + + @Override + public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { + log.tracef( "#handleTestExecutionException(%s, %s)", context.getDisplayName(), throwable ); + + final Object testInstance = context.getRequiredTestInstance(); + final ExtensionContext.Store store = locateExtensionStore( testInstance, context ); + final ServiceRegistryScopeImpl scope = (ServiceRegistryScopeImpl) store.get( REGISTRY_KEY ); + scope.releaseRegistry(); + + throw throwable; + } + + private static class ServiceRegistryScopeImpl implements ServiceRegistryScope, ExtensionContext.Store.CloseableResource { + private BootstrapServiceRegistryProducer bsrProducer; + private ServiceRegistryProducer ssrProducer; + + private StandardServiceRegistry registry; + private boolean active = true; + + public ServiceRegistryScopeImpl(BootstrapServiceRegistryProducer bsrProducer, ServiceRegistryProducer ssrProducer) { + this.bsrProducer = bsrProducer; + this.ssrProducer = ssrProducer; + } + + private StandardServiceRegistry createRegistry() { + BootstrapServiceRegistryBuilder bsrb = new BootstrapServiceRegistryBuilder().enableAutoClose(); + ssrProducer.prepareBootstrapRegistryBuilder(bsrb); + + final org.hibernate.boot.registry.BootstrapServiceRegistry bsr = bsrProducer.produceServiceRegistry( bsrb ); + try { + final StandardServiceRegistryBuilder ssrb = new StandardServiceRegistryBuilder( bsr ); + // we will close it ourselves explicitly. + ssrb.disableAutoClose(); + + return registry = ssrProducer.produceServiceRegistry( ssrb ); + } + catch (Throwable t) { + bsr.close(); + throw t; + } + } + + private void verifyActive() { + if ( !active ) { + throw new IllegalStateException( "ServiceRegistryScope no longer active" ); + } + } + + @Override + public StandardServiceRegistry getRegistry() { + verifyActive(); + + if ( registry == null ) { + registry = createRegistry(); + } + + return registry; + } + + @Override + public void close() { + if ( ! active ) { + return; + } + + log.debugf( "Closing ServiceRegistryScope" ); + + active = false; + + if ( registry != null ) { + releaseRegistry(); + registry = null; + } + } + + private void releaseRegistry() { + if ( registry == null ) { + return; + } + + try { + log.tracef( "#releaseRegistry" ); + StandardServiceRegistryBuilder.destroy( registry ); + } + catch (Exception e) { + log.warn( "Unable to release StandardServiceRegistry", e ); + } + finally { + registry = null; + } + } + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryFunctionalTesting.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryFunctionalTesting.java new file mode 100644 index 000000000000..65ebd558ce92 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryFunctionalTesting.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.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * Composite annotation for applying extensions needed for managing + * a StandardServiceRegistry as part of the test lifecycle. + * + * @author Steve Ebersole + */ +@Inherited +@Retention( RetentionPolicy.RUNTIME ) +@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE}) + +@TestInstance( TestInstance.Lifecycle.PER_CLASS ) + +@ExtendWith( FailureExpectedExtension.class ) +@ExtendWith( ServiceRegistryExtension.class ) +@ExtendWith( ServiceRegistryParameterResolver.class ) +public @interface ServiceRegistryFunctionalTesting { +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryParameterResolver.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryParameterResolver.java new file mode 100644 index 000000000000..9a833928c5d7 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryParameterResolver.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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import org.hibernate.boot.registry.StandardServiceRegistry; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ParameterResolver; + +/** + * @author Steve Ebersole + */ +public class ServiceRegistryParameterResolver implements ParameterResolver { + @Override + public boolean supportsParameter( + ParameterContext parameterContext, + ExtensionContext extensionContext) throws ParameterResolutionException { + return JUnitHelper.supportsParameterInjection( + parameterContext, + StandardServiceRegistry.class, + ServiceRegistryScope.class + ); + } + + @Override + public Object resolveParameter( + ParameterContext parameterContext, + ExtensionContext extensionContext) throws ParameterResolutionException { + final ServiceRegistryScope scope = ServiceRegistryExtension.findServiceRegistryScope( + extensionContext.getRequiredTestInstance(), + extensionContext + ); + + final Class paramType = parameterContext.getParameter().getType(); + if ( paramType.isAssignableFrom( ServiceRegistryScope.class ) ) { + return scope; + } + else if ( paramType.isAssignableFrom( StandardServiceRegistry.class ) ) { + return scope.getRegistry(); + } + + throw new IllegalStateException( + "Unexpected parameter type [" + paramType.getName() + "] for service-registry injection" + ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryProducer.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryProducer.java new file mode 100644 index 000000000000..ff835136f7c8 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryProducer.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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; + +/** + * @author Steve Ebersole + */ +public interface ServiceRegistryProducer { + StandardServiceRegistry produceServiceRegistry(StandardServiceRegistryBuilder builder); + + void prepareBootstrapRegistryBuilder(BootstrapServiceRegistryBuilder bsrb); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryScope.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryScope.java new file mode 100644 index 000000000000..0379a170a237 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryScope.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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.service.Service; + +/** + * @author Steve Ebersole + */ +public interface ServiceRegistryScope { + /** + * Generalized support for running exception-safe code using a ServiceRegistry to + * ensure proper shutdown + */ + static void using(Supplier ssrProducer, Consumer action) { + try (final StandardServiceRegistry ssr = ssrProducer.get()) { + action.accept( () -> ssr ); + } + } + + StandardServiceRegistry getRegistry(); + + default void withService(Class role, Consumer action) { + assert role != null; + + final S service = getRegistry().getService( role ); + + if ( service == null ) { + throw new IllegalArgumentException( "Could not locate requested service - " + role.getName() ); + } + + action.accept( service ); + } + + default R fromService(Class role, Function action) { + assert role != null; + + final S service = getRegistry().getService( role ); + + if ( service == null ) { + throw new IllegalArgumentException( "Could not locate requested service - " + role.getName() ); + } + + return action.apply( service ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryScopeAware.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryScopeAware.java new file mode 100644 index 000000000000..a0ae2ec4c707 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryScopeAware.java @@ -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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +/** + * @author Steve Ebersole + */ +public interface ServiceRegistryScopeAware { + void injectServiceRegistryScope(ServiceRegistryScope registryScope); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactory.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactory.java new file mode 100644 index 000000000000..70593594e1d3 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactory.java @@ -0,0 +1,59 @@ +/* + * 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.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.hibernate.Interceptor; +import org.hibernate.resource.jdbc.spi.StatementInspector; + +import org.hibernate.testing.jdbc.SQLStatementInspector; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * @author Steve Ebersole + */ +@Inherited +@Target( ElementType.TYPE ) +@Retention( RetentionPolicy.RUNTIME ) + +@TestInstance( TestInstance.Lifecycle.PER_CLASS ) + +@ExtendWith( FailureExpectedExtension.class ) +@ExtendWith( ServiceRegistryExtension.class ) +@ExtendWith( ServiceRegistryParameterResolver.class ) + +@ExtendWith( DomainModelExtension.class ) +@ExtendWith( DomainModelParameterResolver.class ) + +@ExtendWith( SessionFactoryExtension.class ) +@ExtendWith( SessionFactoryParameterResolver.class ) +@ExtendWith( SessionFactoryScopeParameterResolver.class ) +public @interface SessionFactory { + String sessionFactoryName() default ""; + + boolean generateStatistics() default false; + boolean exportSchema() default true; + + boolean createSecondarySchemas() default false; + + Class interceptorClass() default Interceptor.class; + + Class statementInspectorClass() default StatementInspector.class; + + /** + * Short hand for {@code statementInspectorClass = org.hibernate.testing.jdbc.SQLStatementInspector.class} + * + * @see SQLStatementInspector + */ + boolean useCollectingStatementInspector() default false; +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryExtension.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryExtension.java new file mode 100644 index 000000000000..751782461012 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryExtension.java @@ -0,0 +1,401 @@ +/* + * 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.testing.orm.junit; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Function; + +import org.hibernate.Interceptor; +import org.hibernate.SessionFactoryObserver; +import org.hibernate.StatelessSession; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.spi.MetadataImplementor; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.internal.util.StringHelper; +import org.hibernate.resource.jdbc.spi.StatementInspector; +import org.hibernate.tool.schema.Action; +import org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator; +import org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator.ActionGrouping; + +import org.hibernate.testing.jdbc.SQLStatementInspector; +import org.hibernate.testing.orm.transaction.TransactionUtil; +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; +import org.junit.jupiter.api.extension.TestInstancePostProcessor; +import org.junit.platform.commons.support.AnnotationSupport; + +import org.jboss.logging.Logger; + +/** + * hibernate-testing implementation of a few JUnit5 contracts to support SessionFactory-based testing, + * including argument injection (or see {@link SessionFactoryScopeAware}) + * + * @see SessionFactoryScope + * @see DomainModelExtension + * + * @author Steve Ebersole + */ +public class SessionFactoryExtension + implements TestInstancePostProcessor, AfterAllCallback, TestExecutionExceptionHandler { + + private static final Logger log = Logger.getLogger( SessionFactoryExtension.class ); + private static final String SESSION_FACTORY_KEY = SessionFactoryScope.class.getName(); + + private static ExtensionContext.Store locateExtensionStore(Object testInstance, ExtensionContext context) { + return JUnitHelper.locateExtensionStore( SessionFactoryExtension.class, context, testInstance ); + } + + public static SessionFactoryScope findSessionFactoryScope(Object testInstance, ExtensionContext context) { + final ExtensionContext.Store store = locateExtensionStore( testInstance, context ); + final SessionFactoryScope existing = (SessionFactoryScope) store.get( SESSION_FACTORY_KEY ); + if ( existing != null ) { + return existing; + } + + SessionFactoryProducer producer = null; + + final DomainModelScope domainModelScope = DomainModelExtension.findDomainModelScope( testInstance, context ); + + if ( testInstance instanceof SessionFactoryProducer ) { + producer = (SessionFactoryProducer) testInstance; + } + else if ( ! context.getElement().isPresent() ) { + throw new RuntimeException( "Unable to determine how to handle given ExtensionContext : " + context.getDisplayName() ); + } + else { + final Optional sfAnnWrappper = AnnotationSupport.findAnnotation( + context.getElement().get(), + SessionFactory.class + ); + + if ( sfAnnWrappper.isPresent() ) { + final SessionFactory sessionFactoryConfig = sfAnnWrappper.get(); + + producer = model -> { + try { + final SessionFactoryBuilder sessionFactoryBuilder = model.getSessionFactoryBuilder(); + if ( StringHelper.isNotEmpty( sessionFactoryConfig.sessionFactoryName() ) ) { + sessionFactoryBuilder.applyName( sessionFactoryConfig.sessionFactoryName() ); + } + + if ( sessionFactoryConfig.generateStatistics() ) { + sessionFactoryBuilder.applyStatisticsSupport( true ); + } + + if ( ! sessionFactoryConfig.interceptorClass().equals( Interceptor.class ) ) { + sessionFactoryBuilder.applyInterceptor( sessionFactoryConfig.interceptorClass().newInstance() ); + } + + final Class explicitInspectorClass = sessionFactoryConfig.statementInspectorClass(); + if ( sessionFactoryConfig.useCollectingStatementInspector() ) { + sessionFactoryBuilder.applyStatementInspector( new SQLStatementInspector() ); + } + else if ( ! explicitInspectorClass.equals( StatementInspector.class ) ) { + sessionFactoryBuilder.applyStatementInspector( explicitInspectorClass.getConstructor().newInstance() ); + } + + final SessionFactoryImplementor sessionFactory = (SessionFactoryImplementor) sessionFactoryBuilder.build(); + + if ( sessionFactoryConfig.exportSchema() ) { + prepareSchemaExport( sessionFactory, model, sessionFactoryConfig.createSecondarySchemas() ); + } + + return sessionFactory; + } + catch (Exception e) { + throw new RuntimeException( "Could not build SessionFactory", e ); + } + }; + } + } + + if ( producer == null ) { + throw new IllegalStateException( "Could not determine SessionFactory producer" ); + } + + final SessionFactoryScopeImpl sfScope = new SessionFactoryScopeImpl( + domainModelScope, + producer + ); + + locateExtensionStore( testInstance, context ).put( SESSION_FACTORY_KEY, sfScope ); + + if ( testInstance instanceof SessionFactoryScopeAware ) { + ( (SessionFactoryScopeAware) testInstance ).injectSessionFactoryScope( sfScope ); + } + + return sfScope; + } + + private static void prepareSchemaExport( + SessionFactoryImplementor sessionFactory, + MetadataImplementor model, + boolean createSecondarySchemas) { + final ActionGrouping grouping = ActionGrouping.interpret( sessionFactory.getProperties() ); + if ( grouping.getDatabaseAction() != Action.NONE || grouping.getScriptAction() != Action.NONE ) { + // the properties contained explicit settings for auto schema tooling; skip here as part of + // @SessionFactory handling + return; + } + + final HashMap settings = new HashMap<>( sessionFactory.getProperties() ); + settings.put( AvailableSettings.JAKARTA_HBM2DDL_DATABASE_ACTION, Action.CREATE_DROP ); + if ( createSecondarySchemas ) { + if ( !( model.getDatabase().getDialect().canCreateSchema() ) ) { + throw new UnsupportedOperationException( + model.getDatabase().getDialect() + " does not support schema creation" ); + } + settings.put( AvailableSettings.JAKARTA_HBM2DDL_CREATE_SCHEMAS, true ); + } + + final StandardServiceRegistry serviceRegistry = model.getMetadataBuildingOptions().getServiceRegistry(); + + SchemaManagementToolCoordinator.process( + model, + serviceRegistry, + settings, + action -> sessionFactory.addObserver( + new SessionFactoryObserver() { + @Override + public void sessionFactoryClosing(org.hibernate.SessionFactory factory) { + action.perform( serviceRegistry ); + } + } + ) + ); + } + + @Override + public void postProcessTestInstance(Object testInstance, ExtensionContext context) { + log.tracef( "#postProcessTestInstance(%s, %s)", testInstance, context.getDisplayName() ); + + findSessionFactoryScope( testInstance, context ); + } + + @Override + public void afterAll(ExtensionContext context) { + log.tracef( "#afterAll(%s)", context.getDisplayName() ); + + final Object testInstance = context.getRequiredTestInstance(); + + if ( testInstance instanceof SessionFactoryScopeAware ) { + ( (SessionFactoryScopeAware) testInstance ).injectSessionFactoryScope( null ); + } + + final SessionFactoryScopeImpl removed = (SessionFactoryScopeImpl) locateExtensionStore( testInstance, context ).remove( SESSION_FACTORY_KEY ); + if ( removed != null ) { + removed.close(); + } + + if ( testInstance instanceof SessionFactoryScopeAware ) { + ( (SessionFactoryScopeAware) testInstance ).injectSessionFactoryScope( null ); + } + } + + @Override + public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { + log.tracef( "#handleTestExecutionException(%s, %s)", context.getDisplayName(), throwable ); + + try { + final Object testInstance = context.getRequiredTestInstance(); + final ExtensionContext.Store store = locateExtensionStore( testInstance, context ); + final SessionFactoryScopeImpl scope = (SessionFactoryScopeImpl) store.get( SESSION_FACTORY_KEY ); + scope.releaseSessionFactory(); + } + catch (Exception ignore) { + } + + throw throwable; + } + + private static class SessionFactoryScopeImpl implements SessionFactoryScope, ExtensionContext.Store.CloseableResource { + private final DomainModelScope modelScope; + private final SessionFactoryProducer producer; + + private SessionFactoryImplementor sessionFactory; + private boolean active = true; + + private SessionFactoryScopeImpl( + DomainModelScope modelScope, + SessionFactoryProducer producer) { + this.modelScope = modelScope; + this.producer = producer; + } + + @Override + public SessionFactoryImplementor getSessionFactory() { + if ( sessionFactory == null ) { + sessionFactory = createSessionFactory(); + } + + return sessionFactory; + } + + @Override + public MetadataImplementor getMetadataImplementor() { + return modelScope.getDomainModel(); + } + + @Override + public StatementInspector getStatementInspector() { + return getSessionFactory().getSessionFactoryOptions().getStatementInspector(); + } + + @Override + public T getStatementInspector(Class type) { + //noinspection unchecked + return (T) getStatementInspector(); + } + + @Override + public SQLStatementInspector getCollectingStatementInspector() { + return getStatementInspector( SQLStatementInspector.class ); + } + + @Override + public void close() { + if ( !active ) { + return; + } + + log.debug( "Closing SessionFactoryScope" ); + + active = false; + releaseSessionFactory(); + } + + public void releaseSessionFactory() { + if ( sessionFactory != null ) { + log.debug( "Releasing SessionFactory" ); + + try { + sessionFactory.close(); + } + catch (Exception e) { + log.warn( "Error closing SF", e ); + } + finally { + sessionFactory = null; + } + } + } + + private SessionFactoryImplementor createSessionFactory() { + if ( !active ) { + throw new IllegalStateException( "SessionFactoryScope is no longer active" ); + } + + log.debug( "Creating SessionFactory" ); + + return producer.produceSessionFactory( modelScope.getDomainModel() ); + } + + public void inSession(Consumer action) { + log.trace( "#inSession(Consumer)" ); + + try (SessionImplementor session = (SessionImplementor) getSessionFactory().openSession()) { + log.trace( "Session opened, calling action" ); + action.accept( session ); + } + finally { + log.trace( "Session close - auto-close block" ); + } + } + + @Override + public T fromSession(Function action) { + log.trace( "#fromSession(Function)" ); + + try (SessionImplementor session = (SessionImplementor) getSessionFactory().openSession()) { + log.trace( "Session opened, calling action" ); + return action.apply( session ); + } + finally { + log.trace( "Session close - auto-close block" ); + } + } + + @Override + public void inTransaction(Consumer action) { + log.trace( "#inTransaction(Consumer)" ); + + try (SessionImplementor session = (SessionImplementor) getSessionFactory().openSession()) { + log.trace( "Session opened, calling action" ); + inTransaction( session, action ); + } + finally { + log.trace( "Session close - auto-close block" ); + } + } + + @Override + public T fromTransaction(Function action) { + log.trace( "#fromTransaction(Function)" ); + + try (SessionImplementor session = (SessionImplementor) getSessionFactory().openSession()) { + log.trace( "Session opened, calling action" ); + return fromTransaction( session, action ); + } + finally { + log.trace( "Session close - auto-close block" ); + } + } + + @Override + public void inTransaction(SessionImplementor session, Consumer action) { + log.trace( "inTransaction(Session,Consumer)" ); + TransactionUtil.inTransaction( session, action ); + } + + @Override + public T fromTransaction(SessionImplementor session, Function action) { + log.trace( "fromTransaction(Session,Function)" ); + return TransactionUtil.fromTransaction( session, action ); + } + + @Override + public void inStatelessSession(Consumer action) { + log.trace( "#inStatelessSession(Consumer)" ); + + try ( final StatelessSession statelessSession = getSessionFactory().openStatelessSession() ) { + log.trace( "StatelessSession opened, calling action" ); + action.accept( statelessSession ); + } + finally { + log.trace( "StatelessSession close - auto-close block" ); + } + } + + @Override + public void inStatelessTransaction(Consumer action) { + log.trace( "#inStatelessTransaction(Consumer)" ); + + try ( final StatelessSession statelessSession = getSessionFactory().openStatelessSession() ) { + log.trace( "StatelessSession opened, calling action" ); + inStatelessTransaction( statelessSession, action ); + } + finally { + log.trace( "StatelessSession close - auto-close block" ); + } + } + + @Override + public void inStatelessTransaction(StatelessSession session, Consumer action) { + log.trace( "inStatelessTransaction(StatelessSession,Consumer)" ); + + TransactionUtil.inTransaction( session, action ); + } + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryFunctionalTesting.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryFunctionalTesting.java new file mode 100644 index 000000000000..138ad4af1dfc --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryFunctionalTesting.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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * Composite annotation for functional tests that require a functioning SessionFactory. + * + * @apiNote Applies support for SessionFactory-based testing. Up to the test to define + * configuration (via {@link ServiceRegistry}), mappings (via {@link DomainModel}) and/or + * SessionFactory-options (via {@link SessionFactory}). Rather than using these other + * annotations, tests could just implement building those individual pieces via + * {@link ServiceRegistryProducer}, {@link DomainModelProducer} and/or {@link SessionFactoryProducer} + * instead. + * + * @see SessionFactoryExtension + * @see DialectFilterExtension + * @see FailureExpectedExtension + * + * @author Steve Ebersole + */ +@Inherited +@Retention( RetentionPolicy.RUNTIME ) +@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE}) + +@TestInstance( TestInstance.Lifecycle.PER_CLASS ) + +@DomainModelFunctionalTesting +@ExtendWith( FailureExpectedExtension.class ) + + +@ExtendWith( SessionFactoryExtension.class ) +public @interface SessionFactoryFunctionalTesting { +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryParameterResolver.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryParameterResolver.java new file mode 100644 index 000000000000..6ee64636610d --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryParameterResolver.java @@ -0,0 +1,33 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import org.hibernate.engine.spi.SessionFactoryImplementor; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ParameterResolver; + +/** + * @author Steve Ebersole + */ +public class SessionFactoryParameterResolver implements ParameterResolver { + @Override + public boolean supportsParameter( + ParameterContext parameterContext, + ExtensionContext extensionContext) throws ParameterResolutionException { + return JUnitHelper.supportsParameterInjection( parameterContext, SessionFactoryImplementor.class ); + } + + @Override + public Object resolveParameter( + ParameterContext parameterContext, + ExtensionContext extensionContext) throws ParameterResolutionException { + return SessionFactoryExtension.findSessionFactoryScope( extensionContext.getRequiredTestInstance(), extensionContext ).getSessionFactory(); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryProducer.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryProducer.java new file mode 100644 index 000000000000..9a6d16c8cf7e --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryProducer.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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import org.hibernate.boot.spi.MetadataImplementor; +import org.hibernate.engine.spi.SessionFactoryImplementor; + +/** + * Contract for something that can build a SessionFactory. + * + * Used by SessionFactoryScopeExtension to create the + * SessionFactoryScope. + * + * Generally speaking, a test class would implement SessionFactoryScopeContainer + * and return the SessionFactoryProducer to be used for those tests. + * The SessionFactoryProducer is then used to build the SessionFactoryScope + * which is injected back into the SessionFactoryScopeContainer + * + * @see SessionFactoryExtension + * @see SessionFactoryScope + * + * @author Steve Ebersole + */ +public interface SessionFactoryProducer { + SessionFactoryImplementor produceSessionFactory(MetadataImplementor model); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryScope.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryScope.java new file mode 100644 index 000000000000..a3ef58448bbf --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryScope.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.testing.orm.junit; + +import java.util.function.Consumer; +import java.util.function.Function; + +import org.hibernate.StatelessSession; +import org.hibernate.boot.spi.MetadataImplementor; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.resource.jdbc.spi.StatementInspector; + +import org.hibernate.testing.jdbc.SQLStatementInspector; + +/** + * @author Steve Ebersole + */ +public interface SessionFactoryScope { + SessionFactoryImplementor getSessionFactory(); + MetadataImplementor getMetadataImplementor(); + StatementInspector getStatementInspector(); + T getStatementInspector(Class type); + SQLStatementInspector getCollectingStatementInspector(); + + void inSession(Consumer action); + void inTransaction(Consumer action); + void inTransaction(SessionImplementor session, Consumer action); + + T fromSession(Function action); + T fromTransaction(Function action); + T fromTransaction(SessionImplementor session, Function action); + + void inStatelessSession(Consumer action); + void inStatelessTransaction(Consumer action); + void inStatelessTransaction(StatelessSession session, Consumer action); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryScopeAware.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryScopeAware.java new file mode 100644 index 000000000000..6ac1dad4a372 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryScopeAware.java @@ -0,0 +1,17 @@ +/* + * 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.testing.orm.junit; + +/** + * @author Steve Ebersole + */ +public interface SessionFactoryScopeAware { + /** + * Callback to inject the SessionFactoryScope into the container + */ + void injectSessionFactoryScope(SessionFactoryScope scope); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryScopeParameterResolver.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryScopeParameterResolver.java new file mode 100644 index 000000000000..c7f4bd2486b3 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryScopeParameterResolver.java @@ -0,0 +1,31 @@ +/* + * 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.testing.orm.junit; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ParameterResolver; + +/** + * @author Steve Ebersole + */ +public class SessionFactoryScopeParameterResolver implements ParameterResolver { + @Override + public boolean supportsParameter( + ParameterContext parameterContext, + ExtensionContext extensionContext) throws ParameterResolutionException { + return JUnitHelper.supportsParameterInjection( parameterContext, SessionFactoryScope.class ); + } + + @Override + public Object resolveParameter( + ParameterContext parameterContext, + ExtensionContext extensionContext) throws ParameterResolutionException { + return SessionFactoryExtension.findSessionFactoryScope( extensionContext.getRequiredTestInstance(), extensionContext ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/Setting.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/Setting.java new file mode 100644 index 000000000000..76f5f0482234 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/Setting.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.testing.orm.junit; + +/** + * A setting for use in other annotations to define settings for various things. + */ +public @interface Setting { + /** + * The setting name. Often a constant from {@link org.hibernate.cfg.AvailableSettings} + */ + String name(); + + /** + * The setting value + */ + String value(); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SettingProvider.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SettingProvider.java new file mode 100644 index 000000000000..51c2befaefca --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SettingProvider.java @@ -0,0 +1,19 @@ +/* + * 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.testing.orm.junit; + +/** + * @author Steve Ebersole + */ +public @interface SettingProvider { + interface Provider { + S getSetting(); + } + + String settingName(); + Class> provider(); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SkipForDialect.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SkipForDialect.java new file mode 100644 index 000000000000..736de3150b66 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SkipForDialect.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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.hibernate.dialect.Dialect; + +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * Indicates that the annotated test class/method should be skipped + * when the indicated Dialect is being used. + * + * It is a repeatable annotation + * + * @see SkipForDialectGroup + * + * @author Steve Ebersole + */ +@Inherited +@Retention( RetentionPolicy.RUNTIME ) +@Target({ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +@Repeatable( SkipForDialectGroup.class ) + +@ExtendWith( DialectFilterExtension.class ) +public @interface SkipForDialect { + Class dialectClass(); + boolean matchSubTypes() default false; + String reason() default ""; + + int majorVersion() default -1; + + int minorVersion() default -1; + + int microVersion() default -1; +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SkipForDialectGroup.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SkipForDialectGroup.java new file mode 100644 index 000000000000..9de623d1bfa4 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SkipForDialectGroup.java @@ -0,0 +1,29 @@ +/* + * 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.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * Grouping annotation for {@link SkipForDialect} + * + * @author Steve Ebersole + */ +@Inherited +@Retention( RetentionPolicy.RUNTIME ) +@Target({ElementType.TYPE, ElementType.METHOD}) + +@ExtendWith( DialectFilterExtension.class ) +public @interface SkipForDialectGroup { + SkipForDialect[] value(); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/TestingUtil.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/TestingUtil.java new file mode 100644 index 000000000000..fd464d82ba3c --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/TestingUtil.java @@ -0,0 +1,99 @@ +/* + * 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.testing.orm.junit; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.platform.commons.support.AnnotationSupport; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * @author Steve Ebersole + */ +public class TestingUtil { + + private TestingUtil() { + } + + public static Optional findEffectiveAnnotation( + ExtensionContext context, + Class annotationType) { + if ( !context.getElement().isPresent() ) { + return Optional.empty(); + } + + final AnnotatedElement annotatedElement = context.getElement().get(); + + final Optional direct = AnnotationSupport.findAnnotation( annotatedElement, annotationType ); + if ( direct.isPresent() ) { + return direct; + } + + if ( context.getTestInstance().isPresent() ) { + return AnnotationSupport.findAnnotation( context.getRequiredTestInstance().getClass(), annotationType ); + } + + return Optional.empty(); + } + + public static List findEffectiveRepeatingAnnotation( + ExtensionContext context, + Class annotationType, + Class groupAnnotationType) { + if ( !context.getElement().isPresent() ) { + return Collections.emptyList(); + } + + final Optional effectiveAnnotation = findEffectiveAnnotation( context, annotationType ); + final Optional effectiveGroupingAnnotation = findEffectiveAnnotation( + context, + groupAnnotationType + ); + + if ( effectiveAnnotation.isPresent() || effectiveGroupingAnnotation.isPresent() ) { + if ( !effectiveGroupingAnnotation.isPresent() ) { + return Collections.singletonList( effectiveAnnotation.get() ); + } + + final List list = new ArrayList<>(); + effectiveAnnotation.ifPresent( list::add ); + + final Method valueMethod; + try { + valueMethod = groupAnnotationType.getDeclaredMethod( "value", null ); + + Collections.addAll( list, (A[]) valueMethod.invoke( effectiveGroupingAnnotation.get() ) ); + } + catch (Exception e) { + throw new RuntimeException( "Could not locate repeated/grouped annotations", e ); + } + + return list; + } + + return Collections.emptyList(); + } + + public static boolean hasEffectiveAnnotation(ExtensionContext context, Class annotationType) { + return findEffectiveAnnotation( context, annotationType ).isPresent(); + } + + @SuppressWarnings("unchecked") + public static T cast(Object thing, Class type) { + assertThat( thing, instanceOf( type ) ); + return type.cast( thing ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/transaction/TransactionUtil.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/transaction/TransactionUtil.java new file mode 100644 index 000000000000..42f04e44e610 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/transaction/TransactionUtil.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.testing.orm.transaction; + +import java.util.function.Consumer; +import java.util.function.Function; + +import org.hibernate.SharedSessionContract; +import org.hibernate.StatelessSession; +import org.hibernate.Transaction; +import org.hibernate.engine.spi.SessionImplementor; + +import org.jboss.logging.Logger; + +import javax.persistence.EntityManager; + +public abstract class TransactionUtil { + private static final Logger log = Logger.getLogger( TransactionUtil.class ); + + public static void inTransaction(SessionImplementor session, Consumer action) { + wrapInTransaction( session, session, action ); + } + + public static void inTransaction(EntityManager entityManager, Consumer action) { + wrapInTransaction( (SharedSessionContract) entityManager, entityManager, action ); + } + + public static void inTransaction(StatelessSession session, Consumer action) { + wrapInTransaction( session, session, action ); + } + + public static R fromTransaction(SessionImplementor session, Function action) { + return wrapInTransaction( session, session, action ); + } + + public static R fromTransaction(EntityManager entityManager, Function action) { + return wrapInTransaction( (SharedSessionContract) entityManager, entityManager, action ); + } + + private static void wrapInTransaction(SharedSessionContract session, T actionInput, Consumer action) { + final Transaction txn = session.beginTransaction(); + log.trace( "Started transaction" ); + + try { + log.trace( "Calling action in txn" ); + action.accept( actionInput ); + log.trace( "Called action - in txn" ); + + if ( !txn.getRollbackOnly() ) { + log.trace( "Committing transaction" ); + txn.commit(); + log.trace( "Committed transaction" ); + } + else { + try { + log.trace( "Rollback transaction marked for rollback only" ); + txn.rollback(); + } + catch (Exception e) { + log.error( "Rollback failure", e ); + } + } + } + catch (Exception e) { + log.tracef( + "Error calling action: %s (%s) - rolling back", + e.getClass().getName(), + e.getMessage() + ); + try { + txn.rollback(); + } + catch (Exception ignore) { + log.trace( "Was unable to roll back transaction" ); + // really nothing else we can do here - the attempt to + // rollback already failed and there is nothing else + // to clean up. + } + + throw e; + } + catch (AssertionError t) { + try { + txn.rollback(); + } + catch (Exception ignore) { + log.trace( "Was unable to roll back transaction" ); + // really nothing else we can do here - the attempt to + // rollback already failed and there is nothing else + // to clean up. + } + throw t; + } + } + + + private static R wrapInTransaction(SharedSessionContract session, T actionInput, Function action) { + log.trace( "Started transaction" ); + Transaction txn = session.beginTransaction(); + try { + log.trace( "Calling action in txn" ); + final R result = action.apply( actionInput ); + log.trace( "Called action - in txn" ); + + log.trace( "Committing transaction" ); + txn.commit(); + log.trace( "Committed transaction" ); + + return result; + } + catch (Exception e) { + log.tracef( + "Error calling action: %s (%s) - rolling back", + e.getClass().getName(), + e.getMessage() + ); + try { + txn.rollback(); + } + catch (Exception ignore) { + log.trace( "Was unable to roll back transaction" ); + // really nothing else we can do here - the attempt to + // rollback already failed and there is nothing else + // to clean up. + } + + throw e; + } + catch (AssertionError t) { + try { + txn.rollback(); + } + catch (Exception ignore) { + log.trace( "Was unable to roll back transaction" ); + // really nothing else we can do here - the attempt to + // rollback already failed and there is nothing else + // to clean up. + } + throw t; + } + } + +} diff --git a/hibernate-vibur/src/test/java/org/hibernate/test/vibur/ViburDBCPConnectionProviderTest.java b/hibernate-vibur/src/test/java/org/hibernate/test/vibur/ViburDBCPConnectionProviderTest.java index 8cb6b7354a26..00970ca0338c 100644 --- a/hibernate-vibur/src/test/java/org/hibernate/test/vibur/ViburDBCPConnectionProviderTest.java +++ b/hibernate-vibur/src/test/java/org/hibernate/test/vibur/ViburDBCPConnectionProviderTest.java @@ -73,16 +73,17 @@ public void setUpPoolAndDatabase(int poolMaxSize, int statementCacheMaxSize) { buildSessionFactory(); doInHibernate(this::sessionFactory, session -> { - addDbRecord(session, "CHRISTIAN", "GABLE"); - addDbRecord(session, "CHRISTIAN", "AKROYD"); - addDbRecord(session, "CHRISTIAN", "NEESON"); - addDbRecord(session, "CAMERON", "NEESON"); - addDbRecord(session, "RAY", "JOHANSSON"); + addDbRecord(session, 1L, "CHRISTIAN", "GABLE"); + addDbRecord(session, 2L, "CHRISTIAN", "AKROYD"); + addDbRecord(session, 3L, "CHRISTIAN", "NEESON"); + addDbRecord(session, 4L, "CAMERON", "NEESON"); + addDbRecord(session, 5L, "RAY", "JOHANSSON"); }); } - private static void addDbRecord(Session session, String firstName, String lastName) { + private static void addDbRecord(Session session, Long id, String firstName, String lastName) { Actor actor = new Actor(); + actor.setId( id ); actor.setFirstName(firstName); actor.setLastName(lastName); session.persist(actor); @@ -145,7 +146,6 @@ private static void executeAndVerifySelect(Session session) { @Entity(name="Actor") public static class Actor { @Id - @GeneratedValue private Long id; private String firstName; diff --git a/hibernate-vibur/src/test/resources/hibernate.properties b/hibernate-vibur/src/test/resources/hibernate.properties index 22936d7e924a..8953491b9c19 100644 --- a/hibernate-vibur/src/test/resources/hibernate.properties +++ b/hibernate-vibur/src/test/resources/hibernate.properties @@ -9,6 +9,7 @@ hibernate.connection.driver_class @jdbc.driver@ hibernate.connection.url @jdbc.url@ hibernate.connection.username @jdbc.user@ hibernate.connection.password @jdbc.pass@ +hibernate.connection.init_sql @connection.init_sql@ hibernate.jdbc.batch_size 10 hibernate.connection.provider_class ViburDBCPConnectionProvider diff --git a/release/release.gradle b/release/release.gradle index 7c95227d6879..0984ccb5027d 100644 --- a/release/release.gradle +++ b/release/release.gradle @@ -108,10 +108,10 @@ task assembleDocumentation(type: Task, dependsOn: [rootProject.project( 'documen task uploadDocumentation(type:Exec, dependsOn: assembleDocumentation) { description = "Uploads documentation to the JBoss doc server" - final String url = "filemgmt.jboss.org:/docs_htdocs/hibernate/orm/${rootProject.ormVersion.family}"; + final String url = "filemgmt-prod-sync.jboss.org:/docs_htdocs/hibernate/orm/${rootProject.ormVersion.family}"; executable 'rsync' - args '-avz', '--links', '--protocol=28', "${documentationDir.absolutePath}/", url + args '--port=2222', '-avz', '--links', "${documentationDir.absolutePath}/", url doFirst { if ( rootProject.ormVersion.isSnapshot ) { @@ -249,29 +249,6 @@ task buildBundles(type: Task, dependsOn: [distZip,distTar]) { description = "Builds all release bundles" } -task uploadBundlesSourceForge(type: Exec, dependsOn: buildBundles) { - description = "Uploads release bundles to SourceForge" - - final String url = "frs.sourceforge.net:/home/frs/project/hibernate/hibernate-orm/${version}"; - - executable 'rsync' - args '-vr', '-e ssh', "${project.buildDir}/distributions/", url - - doFirst { - if ( rootProject.ormVersion.isSnapshot ) { - logger.error( "Cannot perform upload of SNAPSHOT bundles to SourceForge" ); - throw new RuntimeException( "Cannot perform upload of SNAPSHOT bundles to SourceForge" ) - } - else { - logger.lifecycle( "Uploading release bundles to SourceForge..." ) - } - } - - doLast { - logger.lifecycle( 'Done uploading release bundles to SourceForge' ) - } -} - configurations { bundles { description = 'Configuration used to group the archives output from the distribution plugin.' @@ -283,7 +260,7 @@ artifacts { bundles distZip } -task release( dependsOn: [releaseChecks, uploadDocumentation, uploadBundlesSourceForge] ) +task release( dependsOn: [releaseChecks, uploadDocumentation] ) task changeLogFile( dependsOn: [releaseChecks] ) { group = "Release" @@ -330,7 +307,7 @@ task ciRelease( dependsOn: [releaseChecks, addVersionCommit, release] ) { tag = tag.replace( ".Final", "" ) } logger.lifecycle( "Tagging '${tag}'..." ) - executeGitCommand( 'tag', tag ) + executeGitCommand( 'tag', '-a', tag, '-m', "Release $project.ormVersion.fullName" ) } logger.lifecycle( "Adding commit to update version to '${project.developmentVersion}'..." ) diff --git a/tooling/metamodel-generator-jakarta/hibernate-jpamodelgen-jakarta.gradle b/tooling/metamodel-generator-jakarta/hibernate-jpamodelgen-jakarta.gradle index 13d32b668564..7a8dda7385d3 100644 --- a/tooling/metamodel-generator-jakarta/hibernate-jpamodelgen-jakarta.gradle +++ b/tooling/metamodel-generator-jakarta/hibernate-jpamodelgen-jakarta.gradle @@ -1,3 +1,5 @@ +import javax.inject.Inject + /* * Hibernate, Relational Persistence for Idiomatic Java * @@ -10,7 +12,23 @@ description = 'Annotation Processor to generate JPA 3 static metamodel classes' apply from: rootProject.file( 'gradle/published-java-module.gradle' ) configurations { - jakartaeeTransformJars + jakartaeeTransformTool +} + +// we do not want the much of the normal java plugin's behavior +compileJava.enabled false +processResources.enabled false +jar.enabled false +javadocJar.enabled false +sourcesJar.enabled false + +ext { + transformedJarName = project(':hibernate-jpamodelgen').tasks.jar.archiveFileName.get(). + replaceAll( 'hibernate-jpamodelgen', 'hibernate-jpamodelgen-jakarta' ) + + originalTestSrcDir = "${project(':hibernate-jpamodelgen').projectDir}/src/test" + transformedTestSrcDirRelative = 'generated-src/test' + transformedTestSrcDir = "${buildDir}/${transformedTestSrcDirRelative}" } dependencies { @@ -18,13 +36,24 @@ dependencies { compile( libraries.jakarta_jaxb_api ) compile( libraries.jakarta_jaxb_runtime ) - jakartaeeTransformJars 'biz.aQute.bnd:biz.aQute.bnd.transform:5.1.1', + jakartaeeTransformTool 'biz.aQute.bnd:biz.aQute.bnd.transform:5.1.1', 'commons-cli:commons-cli:1.4', 'org.slf4j:slf4j-simple:1.7.30', 'org.slf4j:slf4j-api:1.7.26', 'org.eclipse.transformer:org.eclipse.transformer:0.2.0', 'org.eclipse.transformer:org.eclipse.transformer.cli:0.2.0' + + testCompile project(':hibernate-testing-jakarta') testCompile fileTree(dir: 'libs', include: '*.jar') + testCompile libraries.junit + testCompile libraries.jakarta_jpa + testCompile libraries.jakarta_validation +} + +// +sourceSets.test { + java.srcDir "${project.transformedTestSrcDir}/java" + resources.srcDir "${project.transformedTestSrcDir}/resources" } jar { @@ -59,10 +88,211 @@ jar { } javaexec { - classpath configurations.jakartaeeTransformJars + classpath configurations.jakartaeeTransformTool main = 'org.eclipse.transformer.jakarta.JakartaTransformer' args = transformerArgs } } } +} + +task transformJar(type: JakartaJarTransformation) { + description 'Transforms the hibernate-jpamodelgen jar using the JakartaTransformer tool' + + dependsOn project(':hibernate-jpamodelgen').tasks.jar + mustRunAfter project(':hibernate-jpamodelgen').tasks.jar + + sourceJar project(':hibernate-jpamodelgen').tasks.jar.archiveFile + targetJar tasks.jar.archiveFile.get().asFile +} + +task transformSourcesJar(type: JakartaJarTransformation) { + description 'Transforms the hibernate-jpamodelgen sources jar using the JakartaTransformer tool' + + dependsOn project(':hibernate-jpamodelgen').tasks.sourcesJar + mustRunAfter project(':hibernate-jpamodelgen').tasks.sourcesJar + + sourceJar project(':hibernate-jpamodelgen').tasks.sourcesJar.archiveFile + targetJar tasks.sourcesJar.archiveFile.get().asFile +} + +task transformJavadocJar(type: JakartaJarTransformation) { + description 'Transforms the hibernate-jpamodelgen javadoc jar using the JakartaTransformer tool' + + dependsOn project(':hibernate-jpamodelgen').tasks.javadocJar + mustRunAfter project(':hibernate-jpamodelgen').tasks.javadocJar + + sourceJar project(':hibernate-jpamodelgen').tasks.javadocJar.archiveFile + targetJar tasks.javadocJar.archiveFile.get().asFile +} + +// jpamodelgen tests need access to test sources, so we transform test sources instead of the test JAR. +task transformTestSources(type: JakartaSourcesTransformation) { + description 'Transforms the hibernate-jpamodelgen test sources using the JakartaTransformer tool' + + // Only run this if JavaEE tests compile + dependsOn project(':hibernate-jpamodelgen').tasks.compileTestJava + mustRunAfter project(':hibernate-jpamodelgen').tasks.compileTestJava + + sourceDir project.originalTestSrcDir + targetDir project.transformedTestSrcDir +} + +configurations { + [apiElements, runtimeElements].each { + it.outgoing.artifacts.removeIf { + it.buildDependencies.getDependencies(null).contains(jar) + } + it.outgoing.artifact(tasks.transformJar.targetJar) { + builtBy tasks.transformJar + } + it.outgoing.artifact(tasks.transformSourcesJar.targetJar) { + builtBy tasks.transformSourcesJar + } + it.outgoing.artifact(tasks.transformJavadocJar.targetJar) { + builtBy tasks.transformJavadocJar + } + } +} + +compileTestJava { + dependsOn tasks.transformJar + dependsOn tasks.transformTestSources + + mustRunAfter tasks.transformJar + mustRunAfter tasks.transformTestSources + + classpath += files( + "${buildDir}/libs/${project.transformedJarName}" + ) + + options.compilerArgs += [ + "-proc:none" + ] +} + +test { + classpath += files( + "${buildDir}/libs/${project.transformedJarName}" + ) + + systemProperty 'file.encoding', 'utf-8' + systemProperty 'sourceBaseDir', "${project.transformedTestSrcDir}/java" + + if ( gradle.ext.javaVersions.test.launcher.asInt() >= 9 ) { + // Weld needs this to generate proxies + jvmArgs( ['--add-opens', 'java.base/java.security=ALL-UNNAMED'] ) + jvmArgs( ['--add-opens', 'java.base/java.lang=ALL-UNNAMED'] ) + } + + maxHeapSize = '3G' + // Allow to exclude specific tests + if (project.hasProperty('excludeTests')) { + filter { + excludeTestsMatching project.property('excludeTests').toString() + } + } +} + +@CacheableTask +abstract class JakartaJarTransformation extends DefaultTask { + private final RegularFileProperty sourceJar; + private final RegularFileProperty targetJar; + + @Inject + JakartaJarTransformation(ObjectFactory objectFactory) { + sourceJar = objectFactory.fileProperty(); + targetJar = objectFactory.fileProperty(); + } + + @InputFile + @PathSensitive( PathSensitivity.RELATIVE ) + RegularFileProperty getSourceJar() { + return sourceJar; + } + + void sourceJar(Object fileReference) { + sourceJar.set( project.file( fileReference ) ) + } + + @OutputFile + RegularFileProperty getTargetJar() { + return targetJar; + } + + void targetJar(Object fileReference) { + targetJar.set( project.file( fileReference ) ) + } + + @TaskAction + void transform() { + project.javaexec( new Action() { + @Override + void execute(JavaExecSpec javaExecSpec) { + javaExecSpec.classpath( getProject().getConfigurations().getByName( "jakartaeeTransformTool" ) ); + javaExecSpec.setMain( "org.eclipse.transformer.jakarta.JakartaTransformer" ); + javaExecSpec.args( + sourceJar.get().getAsFile().getAbsolutePath(), + targetJar.get().getAsFile().getAbsolutePath(), + "-q", + "-tr", getProject().getRootProject().file( "rules/jakarta-renames.properties" ).getAbsolutePath(), + "-tv", getProject().getRootProject().file( "rules/jakarta-versions.properties" ).getAbsolutePath(), + "-td", getProject().getRootProject().file( "rules/jakarta-direct-modelgen.properties" ).getAbsolutePath() + ); + } + }); + } +} + +@CacheableTask +abstract class JakartaSourcesTransformation extends DefaultTask { + private final DirectoryProperty sourceDir; + private final DirectoryProperty targetDir; + + @Inject + JakartaSourcesTransformation(ObjectFactory objectFactory) { + sourceDir = objectFactory.directoryProperty(); + targetDir = objectFactory.directoryProperty(); + } + + @InputDirectory + @PathSensitive( PathSensitivity.RELATIVE ) + DirectoryProperty getSourceDir() { + return sourceDir; + } + + void sourceDir(Object directoryReference) { + sourceDir.set( project.file( directoryReference ) ) + } + + @OutputDirectory + DirectoryProperty getTargetDir() { + return targetDir; + } + + void targetDir(Object directoryReference) { + targetDir.set( project.file( directoryReference ) ) + } + + @TaskAction + void transform() { + project.javaexec( new Action() { + @Override + void execute(JavaExecSpec javaExecSpec) { + javaExecSpec.classpath( getProject().getConfigurations().getByName( "jakartaeeTransformTool" ) ); + javaExecSpec.setMain( "org.eclipse.transformer.jakarta.JakartaTransformer" ); + javaExecSpec.args( + sourceDir.get().getAsFile().getAbsolutePath(), + targetDir.get().getAsFile().getAbsolutePath(), + // The transformer won't run if the target directory exist, + // except if we allow it to overwrite the target directory through this option. + '-o', + "-q", + "-tr", getProject().getRootProject().file( "rules/jakarta-renames.properties" ).getAbsolutePath(), + "-tv", getProject().getRootProject().file( "rules/jakarta-versions.properties" ).getAbsolutePath(), + "-td", getProject().getRootProject().file( "rules/jakarta-direct-modelgen.properties" ).getAbsolutePath() + ); + } + }); + } } \ No newline at end of file