diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 000000000000..a2f14f0d53f0 --- /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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@7e3036b9cd87fc26dd06747b7aa4b96c27aaef3a # v3.28.4 + 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@7e3036b9cd87fc26dd06747b7aa4b96c27aaef3a # v3.28.4 + + # â„šī¸ 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@7e3036b9cd87fc26dd06747b7aa4b96c27aaef3a # v3.28.4 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/contributor-build.yml b/.github/workflows/contributor-build.yml index e6ccfdb63ed3..1a32833b4001 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,37 +13,43 @@ 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 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - name: Reclaim Disk Space @@ -53,16 +59,17 @@ jobs: RDBMS: ${{ matrix.rdbms }} run: ci/database-start.sh - name: Set up Java 8 - uses: actions/setup-java@v1 + uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0 with: - java-version: 1.8 + distribution: 'temurin' + java-version: '8' - 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 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 id: cache-maven with: path: | @@ -77,7 +84,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@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 if: failure() with: name: test-reports-java8-${{ matrix.rdbms }} @@ -86,44 +93,3 @@ jobs: ./**/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 diff --git a/.release/.gitignore b/.release/.gitignore new file mode 100644 index 000000000000..859e52ddf899 --- /dev/null +++ b/.release/.gitignore @@ -0,0 +1,3 @@ +# The folder into which we checkout our release scripts into +* +!.gitignore 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..df33316ae2bd 100644 --- a/build.gradle +++ b/build.gradle @@ -14,25 +14,17 @@ 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' } } 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 'org.hibernate.build.xjc' version '2.0.1' apply false - id 'org.hibernate.build.maven-repo-auth' version '3.0.3' apply false id 'biz.aQute.bnd' version '5.1.1' apply false } -ext { - sonatypeOssrhUser = project.findProperty( 'SONATYPE_OSSRH_USER' ) - sonatypeOssrhPassword = project.findProperty( 'SONATYPE_OSSRH_PASSWORD' ) -} - File versionFile = file( "${rootProject.projectDir}/gradle/version.properties" ) ext { @@ -51,15 +43,6 @@ ext { group = 'org.hibernate' version = project.ormVersion.fullName -nexusPublishing { - repositories { - sonatype { - username = project.sonatypeOssrhUser - password = project.sonatypeOssrhPassword - } - } -} - allprojects { repositories { mavenCentral() 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/release/Jenkinsfile b/ci/release/Jenkinsfile new file mode 100644 index 000000000000..59c9c661b7bf --- /dev/null +++ b/ci/release/Jenkinsfile @@ -0,0 +1,286 @@ +#! /usr/bin/groovy +/* + * See https://github.com/hibernate/hibernate-jenkins-pipeline-helpers + */ +@Library('hibernate-jenkins-pipeline-helpers') _ + +import org.hibernate.jenkins.pipeline.helpers.version.Version + +// -------------------------------------------- +// Global build configuration +env.PROJECT = "orm" +env.JIRA_KEY = "HHH" +def RELEASE_ON_SCHEDULE = false // Set to `true` *only* on branches where you want a scheduled release. + +print "INFO: env.PROJECT = ${env.PROJECT}" +print "INFO: env.JIRA_KEY = ${env.JIRA_KEY}" + +// -------------------------------------------- +// Build conditions + +// 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 = 'NOT_BUILT' + return +} + +def manualRelease = currentBuild.getBuildCauses().toString().contains( 'UserIdCause' ) +def cronRelease = currentBuild.getBuildCauses().toString().contains( 'TimerTriggerCause' ) + +// Only do automatic release on branches where we opted in +if ( !manualRelease && !cronRelease ) { + print "INFO: Build skipped because automated releases on push are disabled on this branch." + currentBuild.result = 'NOT_BUILT' + return +} + +if ( !manualRelease && cronRelease && !RELEASE_ON_SCHEDULE ) { + print "INFO: Build skipped because automated releases are disabled on this branch. See constant RELEASE_ON_SCHEDULE in ci/release/Jenkinsfile" + currentBuild.result = 'NOT_BUILT' + return +} + +// -------------------------------------------- +// Reusable methods + +def checkoutReleaseScripts() { + dir('.release/scripts') { + checkout scmGit(branches: [[name: '*/main']], extensions: [], + userRemoteConfigs: [[credentialsId: 'ed25519.Hibernate-CI.github.com', + url: 'https://github.com/hibernate/hibernate-release-scripts.git']]) + } +} + + +// -------------------------------------------- +// Pipeline + +pipeline { + agent { + label 'Release' + } + triggers { + // Run every week Sunday midnight + cron('0 0 * * 0') + } + tools { + jdk 'OpenJDK 11 Latest' + } + options { + buildDiscarder logRotator(daysToKeepStr: '30', numToKeepStr: '10') + disableConcurrentBuilds(abortPrevious: false) + preserveStashes() + } + parameters { + string( + name: 'RELEASE_VERSION', + defaultValue: '', + description: 'The version to be released, e.g. 6.2.1.Final. Mandatory for manual releases, to prevent mistakes.', + trim: true + ) + string( + name: 'DEVELOPMENT_VERSION', + defaultValue: '', + description: 'The next version to be used after the release, e.g. 6.2.2-SNAPSHOT. If not set, determined automatically from the release version.', + trim: true + ) + booleanParam( + name: 'RELEASE_DRY_RUN', + defaultValue: false, + description: 'If true, just simulate the release, without pushing any commits or tags, and without uploading any artifacts or documentation.' + ) + } + stages { + stage('Release check') { + steps { + script { + print "INFO: params.RELEASE_VERSION = ${params.RELEASE_VERSION}" + print "INFO: params.DEVELOPMENT_VERSION = ${params.DEVELOPMENT_VERSION}" + print "INFO: params.RELEASE_DRY_RUN? = ${params.RELEASE_DRY_RUN}" + + checkoutReleaseScripts() + + def currentVersion = Version.parseDevelopmentVersion( sh( + script: ".release/scripts/determine-current-version.sh ${env.PROJECT}", + returnStdout: true + ).trim() ) + echo "Workspace version: ${currentVersion}" + + def releaseVersion + def developmentVersion + + def lastCommitter = sh(script: 'git show -s --format=\'%an\'', returnStdout: true).trim() + def secondLastCommitter = sh(script: 'git show -s --format=\'%an\' HEAD~1', returnStdout: true).trim() + def isCiLastCommiter = lastCommitter == 'Hibernate-CI' && secondLastCommitter == 'Hibernate-CI' + + echo "Last two commits were performed by '${lastCommitter}'/'${secondLastCommitter}'." + echo "Is 'Hibernate-CI' the last commiter: '${isCiLastCommiter}'." + + if ( manualRelease ) { + echo "Release was requested manually" + + if ( !params.RELEASE_VERSION ) { + throw new IllegalArgumentException( + 'Missing value for parameter RELEASE_VERSION. This parameter must be set explicitly to prevent mistakes.' + ) + } + releaseVersion = Version.parseReleaseVersion( params.RELEASE_VERSION ) + + if ( !releaseVersion.toString().startsWith( currentVersion.family + '.' ) ) { + throw new IllegalArgumentException( "RELEASE_VERSION = $releaseVersion, which is different from the family of CURRENT_VERSION = $currentVersion. Did you make a mistake?" ) + } + } + else { + echo "Release was triggered automatically" + + // Avoid doing an automatic release for commits from a release + + if (isCiLastCommiter) { + print "INFO: Automatic release skipped because last commits were for the previous release" + currentBuild.getRawBuild().getExecutor().interrupt(Result.NOT_BUILT) + sleep(1) // Interrupt is not blocking and does not take effect immediately. + return + } + + releaseVersion = Version.parseReleaseVersion( sh( + script: ".release/scripts/determine-release-version.sh ${currentVersion}", + returnStdout: true + ).trim() ) + } + echo "Release version: ${releaseVersion}" + + if ( !params.DEVELOPMENT_VERSION ) { + developmentVersion = Version.parseDevelopmentVersion( sh( + script: ".release/scripts/determine-development-version.sh ${releaseVersion}", + returnStdout: true + ).trim() ) + } + else { + developmentVersion = Version.parseDevelopmentVersion( params.DEVELOPMENT_VERSION ) + } + echo "Development version: ${developmentVersion}" + + env.RELEASE_VERSION = releaseVersion.toString() + env.DEVELOPMENT_VERSION = developmentVersion.toString() + env.SCRIPT_OPTIONS = params.RELEASE_DRY_RUN ? "-d" : "" + env.JRELEASER_DRY_RUN = params.RELEASE_DRY_RUN + + // Determine version id to check if Jira version exists + sh ".release/scripts/determine-jira-version-id.sh ${env.JIRA_KEY} ${releaseVersion.withoutFinalQualifier}" + } + } + } + stage('Release prepare') { + steps { + script { + checkoutReleaseScripts() + + configFileProvider([ + configFile(fileId: 'release.config.ssh', targetLocation: "${env.HOME}/.ssh/config"), + configFile(fileId: 'release.config.ssh.knownhosts', targetLocation: "${env.HOME}/.ssh/known_hosts") + ]) { + sshagent(['ed25519.Hibernate-CI.github.com', 'hibernate-ci.frs.sourceforge.net']) { + // set release version + // update changelog from JIRA + // tags the version + // changes the version to the provided development version + withEnv([ + "DISABLE_REMOTE_GRADLE_CACHE=true", + // Increase the amount of memory for this part since asciidoctor doc rendering consumes a lot of metaspace + "GRADLE_OPTS=-Dorg.gradle.jvmargs='-Dlog4j2.disableJmx -Xmx4g -XX:MaxMetaspaceSize=768m -XX:+HeapDumpOnOutOfMemoryError -Duser.language=en -Duser.country=US -Duser.timezone=UTC -Dfile.encoding=UTF-8'" + ]) { + sh ".release/scripts/prepare-release.sh -j -b ${env.GIT_BRANCH} -v ${env.DEVELOPMENT_VERSION} ${env.PROJECT} ${env.RELEASE_VERSION}" + } + } + } + } + } + } + stage('Publish') { + steps { + script { + checkoutReleaseScripts() + + configFileProvider([ + configFile(fileId: 'release.config.ssh', targetLocation: "${env.HOME}/.ssh/config"), + configFile(fileId: 'release.config.ssh.knownhosts', targetLocation: "${env.HOME}/.ssh/known_hosts") + ]) { + withCredentials([ + usernamePassword(credentialsId: 'central.sonatype.com', passwordVariable: 'JRELEASER_MAVENCENTRAL_TOKEN', usernameVariable: 'JRELEASER_MAVENCENTRAL_USERNAME'), + // https://docs.gradle.org/current/userguide/publishing_gradle_plugins.html#account_setup + usernamePassword(credentialsId: 'gradle-plugin-portal-api-key', passwordVariable: 'GRADLE_PUBLISH_SECRET', usernameVariable: 'GRADLE_PUBLISH_KEY'), + gitUsernamePassword(credentialsId: 'username-and-token.Hibernate-CI.github.com', gitToolName: 'Default'), + file(credentialsId: 'release.gpg.private-key', variable: 'RELEASE_GPG_PRIVATE_KEY_PATH'), + string(credentialsId: 'release.gpg.passphrase', variable: 'JRELEASER_GPG_PASSPHRASE'), + string(credentialsId: 'Hibernate-CI.github.com', variable: 'JRELEASER_GITHUB_TOKEN') + ]) { + sshagent(['ed25519.Hibernate-CI.github.com', 'jenkins.in.relation.to', 'hibernate-ci.frs.sourceforge.net']) { + // performs documentation upload and Sonatype release + // push to github + withEnv([ + "DISABLE_REMOTE_GRADLE_CACHE=true" + ]) { + def notesFiles = findFiles(glob: 'release_notes.md') + if ( notesFiles.length < 1 ) { + throw new IllegalStateException( "Could not locate `release_notes.md`" ) + } + if ( notesFiles.length > 1 ) { + throw new IllegalStateException( "Located more than 1 `release_notes.md`" ) + } + + sh ".release/scripts/publish.sh -j --notes=${notesFiles[0].path} ${env.SCRIPT_OPTIONS} ${env.PROJECT} ${env.RELEASE_VERSION} ${env.DEVELOPMENT_VERSION} ${env.GIT_BRANCH} " + } + } + } + } + } + } + } + stage('Release on Jira') { + steps { + script { + checkoutReleaseScripts() + + withCredentials([string(credentialsId: 'release-webhook.hibernate.atlassian.net', variable: 'JIRA_WEBHOOK_SECRET')]) { + sh ".release/scripts/jira-release.sh ${env.SCRIPT_OPTIONS} ${env.PROJECT} ${env.RELEASE_VERSION} ${env.DEVELOPMENT_VERSION}" + } + } + } + } + stage('Update website') { + steps { + script { + checkoutReleaseScripts() + + configFileProvider([ + configFile(fileId: 'release.config.ssh', targetLocation: "${env.HOME}/.ssh/config"), + configFile(fileId: 'release.config.ssh.knownhosts', targetLocation: "${env.HOME}/.ssh/known_hosts") + ]) { + withCredentials([ + gitUsernamePassword(credentialsId: 'username-and-token.Hibernate-CI.github.com', gitToolName: 'Default') + ]) { + sshagent( ['ed25519.Hibernate-CI.github.com'] ) { + dir( '.release/hibernate.org' ) { + checkout scmGit( + branches: [[name: '*/production']], + extensions: [], + userRemoteConfigs: [[credentialsId: 'ed25519.Hibernate-CI.github.com', url: 'https://github.com/hibernate/hibernate.org.git']] + ) + sh "../scripts/website-release.sh ${env.SCRIPT_OPTIONS} ${env.PROJECT} ${env.RELEASE_VERSION}" + } + } + } + } + } + } + } + } + post { + always { + configFileProvider([configFile(fileId: 'job-configuration.yaml', variable: 'JOB_CONFIGURATION_FILE')]) { + notifyBuildResult maintainers: (String) readYaml(file: env.JOB_CONFIGURATION_FILE).notification?.email?.recipients + } + } + } +} diff --git a/ci/snapshot-publish.Jenkinsfile b/ci/snapshot-publish.Jenkinsfile new file mode 100644 index 000000000000..0059d95d0a96 --- /dev/null +++ b/ci/snapshot-publish.Jenkinsfile @@ -0,0 +1,74 @@ +/* + * 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 +} + +def checkoutReleaseScripts() { + dir('.release/scripts') { + checkout scmGit(branches: [[name: '*/main']], extensions: [], + userRemoteConfigs: [[credentialsId: 'ed25519.Hibernate-CI.github.com', + url: 'https://github.com/hibernate/hibernate-release-scripts.git']]) + } +} + +pipeline { + agent { + label 'Release' + } + 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 { + script { + withCredentials([ + // https://github.com/gradle-nexus/publish-plugin#publishing-to-maven-central-via-sonatype-ossrh + // https://docs.gradle.org/current/samples/sample_publishing_credentials.html#:~:text=via%20environment%20variables + usernamePassword(credentialsId: 'central.sonatype.com', passwordVariable: 'ORG_GRADLE_PROJECT_snapshotsPassword', usernameVariable: 'ORG_GRADLE_PROJECT_snapshotsUsername'), + string(credentialsId: 'Hibernate-CI.github.com', variable: 'JRELEASER_GITHUB_TOKEN'), + // https://docs.gradle.org/current/userguide/publishing_gradle_plugins.html#account_setup + usernamePassword(credentialsId: 'gradle-plugin-portal-api-key', passwordVariable: 'GRADLE_PUBLISH_SECRET', usernameVariable: 'GRADLE_PUBLISH_KEY'), + gitUsernamePassword(credentialsId: 'username-and-token.Hibernate-CI.github.com', gitToolName: 'Default') + ]) { + withEnv([ + "DISABLE_REMOTE_GRADLE_CACHE=true" + ]) { + checkoutReleaseScripts() + def version = sh( + script: ".release/scripts/determine-current-version.sh orm", + returnStdout: true + ).trim() + echo "Current version: '${version}'" + sh "bash -xe .release/scripts/snapshot-deploy.sh orm ${version}" + } + } + } + } + } + } + post { + always { + configFileProvider([configFile(fileId: 'job-configuration.yaml', variable: 'JOB_CONFIGURATION_FILE')]) { + notifyBuildResult maintainers: (String) readYaml(file: env.JOB_CONFIGURATION_FILE).notification?.email?.recipients + } + } + } +} 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/published-java-module.gradle b/gradle/published-java-module.gradle index df0fd448b2c5..58201f9037bc 100644 --- a/gradle/published-java-module.gradle +++ b/gradle/published-java-module.gradle @@ -165,6 +165,8 @@ publishing { task ciBuild( dependsOn: [test, publish] ) -task release( dependsOn: [test, publishToSonatype] ) -publishToSonatype.mustRunAfter test +task releasePrepare( dependsOn: [publishAllPublicationsToStagingRepository] ) { + group = "Release" + description = "Performs a release: the hibernate version is set and the changelog.txt file updated, the changes are pushed to github, then the release is performed, tagged and the hibernate version is set to the development one." +} diff --git a/gradle/publishing-repos.gradle b/gradle/publishing-repos.gradle index b417e1eba3ca..bc3c34a5857a 100644 --- a/gradle/publishing-repos.gradle +++ b/gradle/publishing-repos.gradle @@ -6,19 +6,22 @@ */ apply plugin: 'maven-publish' -if ( rootProject.ormVersion.isSnapshot ) { - apply plugin: 'org.hibernate.build.maven-repo-auth' -} publishing { publications { publishedArtifacts( MavenPublication ) } - repositories { maven { - name 'jboss-snapshots-repository' - url 'https://repository.jboss.org/nexus/content/repositories/snapshots' + name = "staging" + url = rootProject.layout.buildDirectory.dir("staging-deploy${File.separator}maven") + } + maven { + name = 'snapshots' + url = "https://central.sonatype.com/repository/maven-snapshots/" + // So that Gradle uses the `ORG_GRADLE_PROJECT_snapshotsPassword` / `ORG_GRADLE_PROJECT_snapshotsUsername` + // env variables to read the username/password for the `snapshots` repository publishing: + credentials(PasswordCredentials) } } } 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-infinispan/hibernate-infinispan.gradle b/hibernate-infinispan/hibernate-infinispan.gradle index 24393e7568ed..3773c872e776 100644 --- a/hibernate-infinispan/hibernate-infinispan.gradle +++ b/hibernate-infinispan/hibernate-infinispan.gradle @@ -10,7 +10,6 @@ description = '(deprecated - use org.infinispan:infinispan-hibernate-cache-v53 i apply from: rootProject.file( 'gradle/publishing-repos.gradle' ) apply from: rootProject.file( 'gradle/publishing-pom.gradle' ) apply plugin: 'maven-publish' -//apply plugin: 'org.hibernate.build.maven-repo-auth' ext { relocatedGroupId = 'org.infinispan' @@ -30,5 +29,3 @@ publishing { } } } - -task release( dependsOn: publishToSonatype ) \ No newline at end of file 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..89775dadb37e 100644 --- a/release/release.gradle +++ b/release/release.gradle @@ -11,51 +11,10 @@ import groovy.json.JsonSlurper apply plugin: 'idea' apply plugin: 'distribution' -ext { - if ( !project.hasProperty( 'gitRemote' ) ) { - gitRemote = 'origin' - } -} - idea.module { } -final File documentationDir = mkdir( "${project.buildDir}/documentation" ); - -task releaseChecks() { - doFirst { - if ( !project.hasProperty('releaseVersion') || !project.hasProperty('developmentVersion') - || !project.hasProperty('gitRemote') ||!project.hasProperty('gitBranch') ) { - throw new GradleException( - "Release tasks require all of the following properties to be set:" - + "'releaseVersion', 'developmentVersion', 'gitRemote', 'gitBranch'." - ) - } - - logger.lifecycle( "Checking that the working tree is clean..." ) - String uncommittedFiles = executeGitCommand( 'status', '--porcelain' ) - if ( !uncommittedFiles.isEmpty() ) { - throw new GradleException( - "Cannot release because there are uncommitted or untracked files in the working tree." - + "\nCommit or stash your changes first." - + "\nUncommitted files:\n" + uncommittedFiles - ); - } - - logger.lifecycle( "Switching to branch '${project.gitBranch}'..." ) - executeGitCommand( 'checkout', project.gitBranch ) - - logger.lifecycle( "Checking that all commits are pushed..." ) - String diffWithUpstream = executeGitCommand( 'diff', '@{u}' ) - if ( !diffWithUpstream.isEmpty() ) { - throw new GradleException( - "Cannot release because there are commits on the branch to release that haven't been pushed yet." - + "\nPush your commits to the branch to release first." - ); - } - - } -} +final File documentationDir = mkdir( "${rootProject.buildDir}/staging-deploy/documentation" ); /** * Assembles all documentation into the {buildDir}/documentation directory. @@ -102,370 +61,12 @@ task assembleDocumentation(type: Task, dependsOn: [rootProject.project( 'documen } } -/** - * Upload the documentation to the JBoss doc server - */ -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}"; - - executable 'rsync' - args '-avz', '--links', '--protocol=28', "${documentationDir.absolutePath}/", url - - doFirst { - if ( rootProject.ormVersion.isSnapshot ) { - logger.error( "Cannot perform upload of SNAPSHOT documentation" ); - throw new RuntimeException( "Cannot perform upload of SNAPSHOT documentation" ); - } - else { - logger.lifecycle( "Uploading documentation [{$url}]..." ) - } - } - - doLast { - logger.lifecycle( 'Done uploading documentation' ) - } -} - - -/** - * Configuration of the distribution plugin, used to build release bundle as both ZIP and TGZ - */ -distributions { - main { - baseName = 'hibernate-release' - contents { - from rootProject.file( 'lgpl.txt' ) - from rootProject.file( 'changelog.txt' ) - from rootProject.file( 'hibernate_logo.gif' ) - - into('lib/required') { - from parent.project( 'hibernate-core' ).configurations.provided.files { dep -> dep.name == 'jta' } - from parent.project( 'hibernate-core' ).configurations.runtime - from parent.project( 'hibernate-core' ).configurations.archives.allArtifacts.files.filter{ file -> !file.name.endsWith('-sources.jar') } - } - -// into( 'lib/jpa' ) { -// from parent.project( 'hibernate-entitymanager' ).configurations.archives.allArtifacts.files.filter{ file -> !file.name.endsWith('-sources.jar') } -// } - - into( 'lib/spatial' ) { - from( - ( parent.project( 'hibernate-spatial' ).configurations.archives.allArtifacts.files.filter{ file -> !file.name.endsWith('-sources.jar') } - + parent.project( 'hibernate-spatial' ).configurations.runtime ) - - parent.project( 'hibernate-core' ).configurations.runtime - - parent.project( 'hibernate-core' ).configurations.archives.allArtifacts.files - ) - } - - into( 'lib/jpa-metamodel-generator' ) { - from parent.project( 'hibernate-jpamodelgen' ).configurations.archives.allArtifacts.files.filter{ file -> !file.name.endsWith('-sources.jar') } - } - - into( 'lib/envers' ) { - from( - ( parent.project( 'hibernate-envers' ).configurations.archives.allArtifacts.files.filter{ file -> !file.name.endsWith('-sources.jar') } - + parent.project( 'hibernate-envers' ).configurations.runtime ) - - parent.project( 'hibernate-core' ).configurations.runtime - - parent.project( 'hibernate-core' ).configurations.archives.allArtifacts.files - ) - } - - into( 'lib/osgi' ) { - from( - ( parent.project( 'hibernate-osgi' ).configurations.archives.allArtifacts.files.filter{ file -> !file.name.endsWith('-sources.jar') } - + parent.project( 'hibernate-osgi' ).configurations.runtime ) - - parent.project( 'hibernate-core' ).configurations.runtime - - parent.project( 'hibernate-core' ).configurations.archives.allArtifacts.files - ) - from( - parent.project( 'hibernate-osgi' ).extensions.karaf.features.outputFile - ) - } - - // todo : this closure is problematic as it does not write into the hibernate-release-$project.version directory - // due to http://issues.gradle.org/browse/GRADLE-1450 - [ 'hibernate-agroal', 'hibernate-c3p0', 'hibernate-ehcache', 'hibernate-hikaricp', 'hibernate-jcache', 'hibernate-proxool', 'hibernate-vibur' ].each { feature -> - final String shortName = feature.substring( 'hibernate-'.length() ) -// WORKAROUND http://issues.gradle.org/browse/GRADLE-1450 -// into('lib/optional/' + shortName) { - owner.into('lib/optional/' + shortName) { - from ( - ( parent.project( feature ).configurations.archives.allArtifacts.files.filter{ file -> !file.name.endsWith('-sources.jar') } - + parent.project( feature ).configurations.runtime ) - - parent.project( 'hibernate-core' ).configurations.runtime - - parent.project( 'hibernate-core' ).configurations.archives.allArtifacts.files - ) - } - } - - into('documentation') { - from documentationDir - } - - into( 'project' ) { - from ( rootProject.projectDir ) { - exclude( '.git' ) - exclude( '.gitignore' ) - exclude( 'changelog.txt' ) - exclude( 'lgpl.txt' ) - exclude( 'hibernate_logo.gif' ) - exclude( 'tagRelease.sh' ) - exclude( 'gradlew' ) - exclude( 'gradlew.bat' ) - exclude( 'wrapper/*' ) - exclude( '**/.gradle/**' ) - exclude( '**/target/**' ) - exclude( '.idea' ) - exclude( '**/*.ipr' ) - exclude( '**/*.iml' ) - exclude( '**/*.iws' ) - exclude( '**/atlassian-ide-plugin.xml' ) - exclude( '**/.classpath' ) - exclude( '**/.project' ) - exclude( '**/.settings' ) - exclude( '**/.nbattrs' ) - exclude( '**/out/**' ) - exclude( '**/bin/**' ) - exclude( 'build/**' ) - exclude( '*/build/**' ) - } - } - } - } -} - -distZip.dependsOn assembleDocumentation -distTar.dependsOn assembleDocumentation -distTar { - compression = Compression.GZIP -} - -/** - * "virtual" task for building both types of dist bundles - */ -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.' - } -} - -artifacts { - bundles distTar - bundles distZip -} - -task release( dependsOn: [releaseChecks, uploadDocumentation, uploadBundlesSourceForge] ) - -task changeLogFile( dependsOn: [releaseChecks] ) { - group = "Release" - description = "Updates the changelog.txt file" - - doFirst { - logger.lifecycle( "Appending version '${project.releaseVersion}' to changelog..." ) - ChangeLogFile.update( ormVersion.fullName ); - } -} - -task addVersionCommit( dependsOn: [changeLogFile] ) { - group = "Release" - description = "Adds a commit for the released version and push the changes to github" - doFirst{ - logger.lifecycle( "Updating version to '${project.releaseVersion}'..." ) - project.ormVersionFile.text = "hibernateVersion=${project.releaseVersion}" - - logger.lifecycle( "Adding commit to update version to '${project.releaseVersion}'..." ) - executeGitCommand( 'add', '.' ) - executeGitCommand( 'commit', '-m', project.ormVersion.fullName ) - } -} -release.mustRunAfter addVersionCommit - -rootProject.subprojects.each { Project subProject -> - if ( !this.name.equals( subProject.name ) ) { - if ( subProject.tasks.findByName( 'release' ) ) { - this.tasks.release.dependsOn( subProject.tasks.release ) - subProject.tasks.release.mustRunAfter( this.tasks.addVersionCommit ) - } - } -} - -task ciRelease( dependsOn: [releaseChecks, addVersionCommit, release] ) { +task releasePrepare( dependsOn: [assembleDocumentation] ) { group = "Release" description = "Performs a release: the hibernate version is set and the changelog.txt file updated, the changes are pushed to github, then the release is performed, tagged and the hibernate version is set to the development one." - doLast { - String tag = null - if ( !project.hasProperty( 'noTag' ) ) { - tag = project.ormVersion.fullName - // the release is tagged and the tag is pushed to github - if ( tag.endsWith( ".Final" ) ) { - tag = tag.replace( ".Final", "" ) - } - logger.lifecycle( "Tagging '${tag}'..." ) - executeGitCommand( 'tag', tag ) - } - - logger.lifecycle( "Adding commit to update version to '${project.developmentVersion}'..." ) - project.ormVersionFile.text = "hibernateVersion=${project.developmentVersion}" - executeGitCommand( 'add', '.') - executeGitCommand( 'commit', '-m', project.developmentVersion ) - - if ( tag != null ) { - logger.lifecycle("Pushing branch and tag to remote '${project.gitRemote}'...") - executeGitCommand( 'push', '--atomic', project.gitRemote , project.gitBranch, tag ) - } - else { - logger.lifecycle("Pushing branch to remote '${project.gitRemote}'...") - executeGitCommand( 'push', project.gitRemote , project.gitBranch ) - } - } -} - -static String executeGitCommand(Object ... subcommand){ - List command = ['git'] - Collections.addAll( command, subcommand ) - def proc = command.execute() - def code = proc.waitFor() - def stdout = inputStreamToString( proc.getInputStream() ) - def stderr = inputStreamToString( proc.getErrorStream() ) - if ( code != 0 ) { - throw new GradleException( "An error occurred while executing " + command + "\n\nstdout:\n" + stdout + "\n\nstderr:\n" + stderr ) - } - return stdout } -static String inputStreamToString(InputStream inputStream) { - inputStream.withCloseable { ins -> - new BufferedInputStream(ins).withCloseable { bis -> - new ByteArrayOutputStream().withCloseable { buf -> - int result = bis.read(); - while (result != -1) { - buf.write((byte) result); - result = bis.read(); - } - return buf.toString( StandardCharsets.UTF_8.name()); - } - } - } -} - -class ChangeLogFile { - - // Get the Release Notes from Jira and add them to the Hibernate changelog.txt file - static void update(String releaseVersion) { - def text = "" - File changelog = new File( "changelog.txt" ) - def newReleaseNoteBlock = getNewReleaseNoteBlock(releaseVersion) - changelog.eachLine { - line -> - if ( line.startsWith( "Note:" ) ) { - text += line + System.lineSeparator() + System.lineSeparator() + newReleaseNoteBlock - } - else { - text += line + System.lineSeparator() - } - } - changelog.text = text - } - - // Get the Release Notes from Jira - static String getNewReleaseNoteBlock(String releaseVersion) { - def restReleaseVersion; - if ( releaseVersion.endsWith( ".Final" ) ) { - restReleaseVersion = releaseVersion.replace( ".Final", "" ) - } - else { - restReleaseVersion = releaseVersion - } - def apiString = "https://hibernate.atlassian.net/rest/api/2/search/?jql=project=HHH%20AND%20fixVersion=${restReleaseVersion}%20order%20by%20issuetype%20ASC" - def apiUrl = new URL( apiString ) - def jsonReleaseNotes = new JsonSlurper().parse( apiUrl ) - def releaseDate = new Date().format( 'MMMM dd, YYYY' ) - def versionId = getVersionId( jsonReleaseNotes, restReleaseVersion ) - - ReleaseNote releaseNotes = new ReleaseNote( releaseVersion, releaseDate, versionId ) - - def issuetype - jsonReleaseNotes.issues.each { - issue -> - if ( issuetype != issue.fields.issuetype.name ) { - issuetype = issue.fields.issuetype.name - releaseNotes.addEmptyLine(); - releaseNotes.addLine( "** ${issue.fields.issuetype.name}" ) - } - releaseNotes.addLine( " * [" + issue.key + "] - " + issue.fields.summary ) - } - releaseNotes.addEmptyLine() - return releaseNotes.notes - } - - private static getVersionId(jsonReleaseNotes, String restReleaseVersion) { - def fixVersions = jsonReleaseNotes.issues.get( 0 ).fields.fixVersions - - for ( def fixVersion : fixVersions ) { - if ( fixVersion.name.equals( restReleaseVersion ) ) { - return fixVersion.id - } - } - throw new GradleException( "Unable to determine the version id of the current release." ) - } -} - -class ReleaseNote { - String notes; - String notesHeaderSeparator = "------------------------------------------------------------------------------------------------------------------------" - - ReleaseNote(String releaseVersion, String releaseDate, String versionId) { - notes = "Changes in ${releaseVersion} (${releaseDate})" + System.lineSeparator() - addHeaderSeparator() - addEmptyLine() - addLine( "https://hibernate.atlassian.net/projects/HHH/versions/${versionId}" ) - } - - void addLine(String text) { - notes += text + System.lineSeparator() - } - - void addHeaderSeparator() { - addLine( notesHeaderSeparator ) - } - - void addEmptyLine() { - notes += System.lineSeparator() - } - - void addEmptyLines(int numberOfLines) { - for ( i in 1..numberOfLines ) { - notes += System.lineSeparator() - } - } +task releaseGradlePluginPerform { + group = "Release" + description = "This is only a stub task for the release scripts to call. We do not actually publish any Gradle plugins in 5.x.We do publish them in 6+." } - 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