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 dbdb5d6e2d9e..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 @@ -9,43 +9,47 @@ name: Hibernate ORM build on: push: branches: - - 'main' - - 'wip/6.0' + - '5.6' pull_request: branches: - - 'main' - - 'wip/6.0' + - '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 @@ -55,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: | @@ -79,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 }} @@ -88,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..2e84eb08b639 100644 --- a/build.gradle +++ b/build.gradle @@ -14,25 +14,18 @@ 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 'nu.studer.credentials' version '2.2' id 'org.hibernate.build.xjc' version '2.0.1' apply false - id 'org.hibernate.build.maven-repo-auth' version '3.0.3' apply false id 'biz.aQute.bnd' version '5.1.1' apply false } -ext { - sonatypeOssrhUser = project.findProperty( 'SONATYPE_OSSRH_USER' ) - sonatypeOssrhPassword = project.findProperty( 'SONATYPE_OSSRH_PASSWORD' ) -} - File versionFile = file( "${rootProject.projectDir}/gradle/version.properties" ) ext { @@ -51,15 +44,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 35e957f9c6f1..61d9ea5793f6 100644 --- a/changelog.txt +++ b/changelog.txt @@ -3,6 +3,274 @@ 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) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/32029 + +** Bug + * [HHH-15044] - Revert HHH-14826 fix because the provided test was wrong + * [HHH-15041] - H2Dialect does not work properly with h2 2.0.202 due to new DDL type requirements + * [HHH-15014] - H2Dialect does not work properly with h2 2.0.202 on sub selects with tuples + * [HHH-15009] - H2Dialect does not work properly with h2 2.0.202 and updating schema + * [HHH-14985] - H2Dialect does not work properly with h2 2.0.202 on inserts + + +Changes in 5.6.4.Final (January 19, 2022) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/32012 + +** Bug + * [HHH-15032] - Fix backwards incompatible SPI change that happened in 5.6.2 due to introducing SqlStringGenerationContext + * [HHH-15022] - Bug After Upgrade Hibernate from 5.6.1.Final to 5.6.3.Final + * [HHH-15002] - H2Dialect does not work properly with h2 2.0.202 and booleans types + +** Task + * [HHH-15036] - Disable DefaultCatalogAndSchemaTest when testing against MariaDB < 10.3 + * [HHH-15033] - Restrict JNDI lookups to "java" scheme + * [HHH-15031] - Upgrade to ByteBuddy 1.12.7 + * [HHH-15028] - Upgrade to JBoss Logging 3.4.3.Final + * [HHH-15026] - Upgrade to Log4J 2.17.1 + * [HHH-15024] - Upgrade to Jandex 2.4.2.Final + * [HHH-15018] - OracleTypesHelper shouldn't log stacktraces when the Oracle JDBC driver isn't loadable + * [HHH-14998] - Upgrade to GraalVM 21.3.0 + * [HHH-14988] - Upgrade to ByteBuddy 1.12.5 + * [HHH-14987] - Upgrade to Log4j 2.17.0 + + +Changes in 5.6.3.Final (December 15, 2021) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/32006 + +** Bug + * [HHH-14972] - log4j2 <= 2.14.1 has an RCE (CVE-2021-44228) + * [HHH-14948] - Metamodel imports cache increases indefinitely for dynamically generated HQL aliases eventually leading to an OOM + * [HHH-14935] - Type annotation is deprecated without an available replacement + +** Task + * [HHH-14979] - Upgrade to Log4J 2 2.16.0 + + +Changes in 5.6.2.Final (December 08, 2021) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/32001 + +** Bug + * [HHH-14956] - Invalid link to MetadataBuilderContributor javadocs in Configurations docs + * [HHH-14937] - SybaseDialect does not support schema anymore + * [HHH-14936] - JdbcConnectionContext in hibernate-testing throws NPE when user/password are not provided in configuration + * [HHH-14935] - Type annotation is deprecated without an available replacement + * [HHH-14927] - "Current" documentation is 5.5 instead of 5.6 + * [HHH-14926] - fix asciidoc error in 'test-case-guide.adoc' + * [HHH-14922] - Inconsistent precedence of orm.xml implicit catalog/schema over "default_catalog"/"default_schema" + * [HHH-14918] - Key-to-one to id-class entity with key-to-one doesn't work anymore + * [HHH-14916] - JPA Critera query Join on Fetch not working + * [HHH-14540] - Interceptor instance is shared between ORM session and Enver's temporary session resulting in multiple calls. + * [HHH-14211] - @Lob String mapping broken + +** Improvement + * [HHH-14921] - Definition of the default catalog/schema on session factory creation + * [HHH-14903] - Method getConfiguredJdbcBatchSize can be optimised for StatelessSession as well + * [HHH-14897] - Allow ordering with nulls first/last from JPA implementation + +** Task + * [HHH-14938] - Upgrade to MySQL Connector/J 8.0.27 + + Changes in 5.6.1.Final (October 27, 2021) ------------------------------------------------------------------------------------------------------------------------ @@ -308,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..a04d03f69663 --- /dev/null +++ b/ci/release/Jenkinsfile @@ -0,0 +1,275 @@ +#! /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 8 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.filemgmt.jboss.org', '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 release') { + 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([ + // https://github.com/gradle-nexus/publish-plugin#publishing-to-maven-central-via-sonatype-ossrh + // TODO: HHH-19309: + // Once we switch to maven-central publishing (from nexus2) we need to add a new credentials + // to use the following env variable names to set the user/password: + // - JRELEASER_MAVENCENTRAL_USERNAME + // - JRELEASER_MAVENCENTRAL_TOKEN + // Also use the new `credentialsId` for Maven Central, e.g.: + // usernamePassword(credentialsId: '???????', passwordVariable: 'JRELEASER_MAVENCENTRAL_TOKEN', usernameVariable: 'JRELEASER_MAVENCENTRAL_USERNAME'), + usernamePassword(credentialsId: 'ossrh.sonatype.org', passwordVariable: 'JRELEASER_NEXUS2_PASSWORD', usernameVariable: 'JRELEASER_NEXUS2_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', 'hibernate.filemgmt.jboss.org', 'hibernate-ci.frs.sourceforge.net']) { + // performs documentation upload and Sonatype release + // push to github + withEnv([ + "DISABLE_REMOTE_GRADLE_CACHE=true" + ]) { + sh ".release/scripts/publish.sh -j ${env.SCRIPT_OPTIONS} ${env.PROJECT} ${env.RELEASE_VERSION} ${env.DEVELOPMENT_VERSION} ${env.GIT_BRANCH}" + } + } + } + } + } + } + } + stage('Website release') { + 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', 'hibernate.filemgmt.jboss.org', 'hibernate-ci.frs.sourceforge.net'] ) { + 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..e625f7eda044 --- /dev/null +++ b/ci/snapshot-publish.Jenkinsfile @@ -0,0 +1,76 @@ +/* + * 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 + // TODO: HHH-19309: + // Once we switch to maven-central publishing (from nexus2) we need to update credentialsId: + // https://docs.gradle.org/current/samples/sample_publishing_credentials.html#:~:text=via%20environment%20variables + usernamePassword(credentialsId: 'ossrh.sonatype.org', 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/quickstart/tutorials/annotations/src/test/resources/hibernate.cfg.xml b/documentation/src/main/asciidoc/quickstart/tutorials/annotations/src/test/resources/hibernate.cfg.xml index a9590c18778a..8bcad6c099fd 100644 --- a/documentation/src/main/asciidoc/quickstart/tutorials/annotations/src/test/resources/hibernate.cfg.xml +++ b/documentation/src/main/asciidoc/quickstart/tutorials/annotations/src/test/resources/hibernate.cfg.xml @@ -15,7 +15,7 @@ org.h2.Driver - jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1;MVCC=TRUE + jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1 sa diff --git a/documentation/src/main/asciidoc/quickstart/tutorials/basic/src/test/resources/hibernate.cfg.xml b/documentation/src/main/asciidoc/quickstart/tutorials/basic/src/test/resources/hibernate.cfg.xml index 03d39373c3fa..9f7caa7269e6 100644 --- a/documentation/src/main/asciidoc/quickstart/tutorials/basic/src/test/resources/hibernate.cfg.xml +++ b/documentation/src/main/asciidoc/quickstart/tutorials/basic/src/test/resources/hibernate.cfg.xml @@ -15,7 +15,7 @@ org.h2.Driver - jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1;MVCC=TRUE + jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1 sa diff --git a/documentation/src/main/asciidoc/quickstart/tutorials/entitymanager/src/test/resources/META-INF/persistence.xml b/documentation/src/main/asciidoc/quickstart/tutorials/entitymanager/src/test/resources/META-INF/persistence.xml index 0fc952381381..57dcc7ccf471 100644 --- a/documentation/src/main/asciidoc/quickstart/tutorials/entitymanager/src/test/resources/META-INF/persistence.xml +++ b/documentation/src/main/asciidoc/quickstart/tutorials/entitymanager/src/test/resources/META-INF/persistence.xml @@ -18,7 +18,7 @@ - + diff --git a/documentation/src/main/asciidoc/quickstart/tutorials/envers/src/test/resources/META-INF/persistence.xml b/documentation/src/main/asciidoc/quickstart/tutorials/envers/src/test/resources/META-INF/persistence.xml index 45a7dafe73cf..51ccd776ffe6 100644 --- a/documentation/src/main/asciidoc/quickstart/tutorials/envers/src/test/resources/META-INF/persistence.xml +++ b/documentation/src/main/asciidoc/quickstart/tutorials/envers/src/test/resources/META-INF/persistence.xml @@ -18,7 +18,7 @@ - + diff --git a/documentation/src/main/asciidoc/quickstart/tutorials/osgi/datasource-h2.xml b/documentation/src/main/asciidoc/quickstart/tutorials/osgi/datasource-h2.xml index 3bf738fd7a7b..b560a983d8b4 100644 --- a/documentation/src/main/asciidoc/quickstart/tutorials/osgi/datasource-h2.xml +++ b/documentation/src/main/asciidoc/quickstart/tutorials/osgi/datasource-h2.xml @@ -15,7 +15,7 @@ Then copy this file to the deploy folder - + diff --git a/documentation/src/main/asciidoc/quickstart/tutorials/osgi/unmanaged-jpa/src/main/resources/META-INF/persistence.xml b/documentation/src/main/asciidoc/quickstart/tutorials/osgi/unmanaged-jpa/src/main/resources/META-INF/persistence.xml index b0ebfe7053b1..7b03fca6f054 100644 --- a/documentation/src/main/asciidoc/quickstart/tutorials/osgi/unmanaged-jpa/src/main/resources/META-INF/persistence.xml +++ b/documentation/src/main/asciidoc/quickstart/tutorials/osgi/unmanaged-jpa/src/main/resources/META-INF/persistence.xml @@ -15,7 +15,7 @@ - + diff --git a/documentation/src/main/asciidoc/quickstart/tutorials/osgi/unmanaged-native/src/main/resources/hibernate.cfg.xml b/documentation/src/main/asciidoc/quickstart/tutorials/osgi/unmanaged-native/src/main/resources/hibernate.cfg.xml index 7f9f2380c902..1ed56551c9b1 100644 --- a/documentation/src/main/asciidoc/quickstart/tutorials/osgi/unmanaged-native/src/main/resources/hibernate.cfg.xml +++ b/documentation/src/main/asciidoc/quickstart/tutorials/osgi/unmanaged-native/src/main/resources/hibernate.cfg.xml @@ -12,7 +12,7 @@ org.h2.Driver - jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1;MVCC=TRUE + jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1 sa org.hibernate.dialect.H2Dialect diff --git a/documentation/src/main/asciidoc/quickstart/tutorials/osgi/unmanaged-native/src/main/resources/pool-one.properties b/documentation/src/main/asciidoc/quickstart/tutorials/osgi/unmanaged-native/src/main/resources/pool-one.properties index 7e1c4cf1bac6..d3b9463cc6d7 100644 --- a/documentation/src/main/asciidoc/quickstart/tutorials/osgi/unmanaged-native/src/main/resources/pool-one.properties +++ b/documentation/src/main/asciidoc/quickstart/tutorials/osgi/unmanaged-native/src/main/resources/pool-one.properties @@ -5,7 +5,7 @@ # See the lgpl.txt file in the root directory or . # jdbc-0.proxool.alias=pool-one -jdbc-0.proxool.driver-url=jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1;MVCC=TRUE +jdbc-0.proxool.driver-url=jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1 jdbc-0.proxool.driver-class=org.h2.Driver jdbc-0.user=sa jdbc-0.password= diff --git a/documentation/src/main/asciidoc/userguide/chapters/bootstrap/extras/persistence-external.xml b/documentation/src/main/asciidoc/userguide/chapters/bootstrap/extras/persistence-external.xml index e009087fede9..215821ea26c9 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/bootstrap/extras/persistence-external.xml +++ b/documentation/src/main/asciidoc/userguide/chapters/bootstrap/extras/persistence-external.xml @@ -18,7 +18,7 @@ value="org.h2.Driver" /> + value="jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1" /> diff --git a/documentation/src/main/asciidoc/userguide/chapters/bootstrap/extras/persistence.xml b/documentation/src/main/asciidoc/userguide/chapters/bootstrap/extras/persistence.xml index 67e79c3dc042..b61f3160c33f 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/bootstrap/extras/persistence.xml +++ b/documentation/src/main/asciidoc/userguide/chapters/bootstrap/extras/persistence.xml @@ -18,7 +18,7 @@ value="org.h2.Driver" /> + value="jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1" /> 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/main/asciidoc/userguide/chapters/osgi/extras/datasource-h2.xml b/documentation/src/main/asciidoc/userguide/chapters/osgi/extras/datasource-h2.xml index 25898baabf6f..35dec2dcb4c3 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/osgi/extras/datasource-h2.xml +++ b/documentation/src/main/asciidoc/userguide/chapters/osgi/extras/datasource-h2.xml @@ -8,7 +8,7 @@ Then copy this file to the deploy folder - + 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/fetching/FetchingTest.java b/documentation/src/test/java/org/hibernate/userguide/fetching/FetchingTest.java index 2dbec1f5b64d..d3121d620cec 100644 --- a/documentation/src/test/java/org/hibernate/userguide/fetching/FetchingTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/fetching/FetchingTest.java @@ -28,7 +28,9 @@ import org.hibernate.dialect.H2Dialect; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.testing.DialectChecks; import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.RequiresDialectFeature; import org.junit.Test; import org.jboss.logging.Logger; @@ -41,6 +43,7 @@ * @author Vlad Mihalcea */ @RequiresDialect(H2Dialect.class) +@RequiresDialectFeature(value = DialectChecks.NotH2Version2.class, comment = "See https://github.com/h2database/h2database/issues/3338") public class FetchingTest extends BaseEntityManagerFunctionalTestCase { @Override @@ -181,6 +184,12 @@ public static class Employee { read = "decrypt( 'AES', '00', pswd )", write = "encrypt('AES', '00', ?)" ) +// For H2 2.0.202+ one must use the varbinary DDL type +// @Column(name = "pswd", columnDefinition = "varbinary") +// @ColumnTransformer( +// read = "trim(trailing u&'\\0000' from cast(decrypt('AES', '00', pswd ) as character varying))", +// write = "encrypt('AES', '00', ?)" +// ) private String password; private int accessLevel; 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/mapping/identifier/composite/EmbeddedIdDatabaseGeneratedValueTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/composite/EmbeddedIdDatabaseGeneratedValueTest.java index 1bd0ab711c35..f2162253f897 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/composite/EmbeddedIdDatabaseGeneratedValueTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/composite/EmbeddedIdDatabaseGeneratedValueTest.java @@ -11,7 +11,9 @@ import org.hibernate.dialect.H2Dialect; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.testing.DialectChecks; import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.TestForIssue; import org.junit.Test; @@ -22,6 +24,7 @@ * @author Vlad Mihalcea */ @RequiresDialect(H2Dialect.class) +@RequiresDialectFeature(value = DialectChecks.NotH2Version2.class, comment = "CURRENT_TIMESTAMP now returns a TIMESTAMP_WITH_TIME_ZONE") public class EmbeddedIdDatabaseGeneratedValueTest 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 7ddc2ff28f03..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.11.20' + byteBuddyVersion = '1.12.18' agroalVersion = '1.9' @@ -44,9 +46,9 @@ ext { jakartaJaxbRuntimeVersion = '3.0.0' //GraalVM - graalvmVersion = '21.2.0' + graalvmVersion = '21.3.0' - micrometerVersion = '1.6.1' + micrometerVersion = '1.9.3' libraries = [ // Ant @@ -57,7 +59,7 @@ ext { // Annotations commons_annotations: "org.hibernate.common:hibernate-commons-annotations:${hibernateCommonsVersion}", - jandex: 'org.jboss:jandex:2.2.3.Final', + jandex: 'org.jboss:jandex:2.4.2.Final', classmate: 'com.fasterxml:classmate:1.5.1', // Dom4J @@ -88,9 +90,9 @@ ext { jakarta_cdi: 'jakarta.enterprise:jakarta.enterprise.cdi-api:3.0.0', // logging - logging: 'org.jboss.logging:jboss-logging:3.4.2.Final', - logging_annotations: 'org.jboss.logging:jboss-logging-annotations:2.1.0.Final', - logging_processor: 'org.jboss.logging:jboss-logging-processor:2.1.0.Final', + logging: 'org.jboss.logging:jboss-logging:3.4.3.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 - log4j2: "org.apache.logging.log4j:log4j-core:2.14.1", - junit: "junit:junit:${junitVersion}", + 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", + 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..3e9dc94f3dd4 100644 --- a/gradle/published-java-module.gradle +++ b/gradle/published-java-module.gradle @@ -165,6 +165,5 @@ publishing { task ciBuild( dependsOn: [test, publish] ) -task release( dependsOn: [test, publishToSonatype] ) -publishToSonatype.mustRunAfter test +task release( dependsOn: [test] ) diff --git a/gradle/publishing-repos.gradle b/gradle/publishing-repos.gradle index b417e1eba3ca..a0fb56fa24fd 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://oss.sonatype.org/content/repositories/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 5c72c9b3ea00..0b454938b8b3 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -hibernateVersion=5.6.2-SNAPSHOT \ 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/annotations/Type.java b/hibernate-core/src/main/java/org/hibernate/annotations/Type.java index 3290021b77c8..8b8e40ea172b 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/Type.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/Type.java @@ -24,12 +24,9 @@ * * @author Emmanuel Bernard * @author Steve Ebersole - * - * @deprecated 6.0 will introduce a new type-safe {@code CustomType} annotation */ @Target({FIELD, METHOD}) @Retention(RUNTIME) -@Deprecated public @interface Type { /** * The Hibernate type name. Usually the fully qualified name of an implementation class for diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/TypeDef.java b/hibernate-core/src/main/java/org/hibernate/annotations/TypeDef.java index 51f7c8abe7d4..4810824de0e6 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/TypeDef.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/TypeDef.java @@ -28,13 +28,10 @@ * * @author Emmanuel Bernard * @author Steve Ebersole - * - * @deprecated 6.0 will introduce a new series of type-safe annotations to serve the same purpose */ @Target({TYPE, PACKAGE}) @Retention(RUNTIME) @Repeatable(TypeDefs.class) -@Deprecated public @interface TypeDef { /** * The type name. This is the name that would be used in other locations. diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/TypeDefs.java b/hibernate-core/src/main/java/org/hibernate/annotations/TypeDefs.java index cdc175b0e282..6f05951eafc4 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/TypeDefs.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/TypeDefs.java @@ -18,12 +18,9 @@ * * @author Emmanuel Bernard * @author Steve Ebersole - * - * @deprecated 6.0 will introduce a new series of type-safe annotations to serve the same purpose */ @Target({TYPE, PACKAGE}) @Retention(RUNTIME) -@Deprecated public @interface TypeDefs { /** * The grouping of type definitions. 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/internal/InFlightMetadataCollectorImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/InFlightMetadataCollectorImpl.java index 568ef9649817..4f0c817e8c15 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/InFlightMetadataCollectorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/InFlightMetadataCollectorImpl.java @@ -45,7 +45,6 @@ import org.hibernate.boot.model.naming.ImplicitUniqueKeyNameSource; import org.hibernate.boot.model.relational.AuxiliaryDatabaseObject; import org.hibernate.boot.model.relational.Database; -import org.hibernate.boot.model.relational.ExportableProducer; import org.hibernate.boot.model.relational.Namespace; import org.hibernate.boot.model.relational.QualifiedTableName; import org.hibernate.boot.model.source.internal.ImplicitColumnNamingSecondPass; @@ -2198,8 +2197,6 @@ private void processExportableProducers() { // for now we only handle id generators as ExportableProducers final Dialect dialect = getDatabase().getJdbcEnvironment().getDialect(); - final String defaultCatalog = extractName( getDatabase().getDefaultNamespace().getName().getCatalog(), dialect ); - final String defaultSchema = extractName( getDatabase().getDefaultNamespace().getName().getSchema(), dialect ); for ( PersistentClass entityBinding : entityBindingMap.values() ) { if ( entityBinding.isInherited() ) { @@ -2209,8 +2206,6 @@ private void processExportableProducers() { handleIdentifierValueBinding( entityBinding.getIdentifier(), dialect, - defaultCatalog, - defaultSchema, (RootClass) entityBinding ); } @@ -2223,8 +2218,6 @@ private void processExportableProducers() { handleIdentifierValueBinding( ( (IdentifierCollection) collection ).getIdentifier(), dialect, - defaultCatalog, - defaultSchema, null ); } @@ -2233,8 +2226,6 @@ private void processExportableProducers() { private void handleIdentifierValueBinding( KeyValue identifierValueBinding, Dialect dialect, - String defaultCatalog, - String defaultSchema, RootClass entityBinding) { // todo : store this result (back into the entity or into the KeyValue, maybe?) // This process of instantiating the id-generator is called multiple times. @@ -2244,14 +2235,10 @@ private void handleIdentifierValueBinding( final IdentifierGenerator ig = identifierValueBinding.createIdentifierGenerator( getIdentifierGeneratorFactory(), dialect, - defaultCatalog, - defaultSchema, entityBinding ); - if ( ig instanceof ExportableProducer ) { - ( (ExportableProducer) ig ).registerExportables( getDatabase() ); - } + ig.registerExportables( getDatabase() ); } catch (MappingException e) { // ignore this for now. The reasoning being "non-reflective" binding as needed diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/MetadataBuilderImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/MetadataBuilderImpl.java index 61f98f680e9a..6f44c9c7d2e7 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/MetadataBuilderImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/MetadataBuilderImpl.java @@ -499,17 +499,12 @@ public static class MappingDefaultsImpl implements MappingDefaults { public MappingDefaultsImpl(StandardServiceRegistry serviceRegistry) { final ConfigurationService configService = serviceRegistry.getService( ConfigurationService.class ); - this.implicitSchemaName = configService.getSetting( - AvailableSettings.DEFAULT_SCHEMA, - StandardConverters.STRING, - null - ); - - this.implicitCatalogName = configService.getSetting( - AvailableSettings.DEFAULT_CATALOG, - StandardConverters.STRING, - null - ); + // AvailableSettings.DEFAULT_SCHEMA and AvailableSettings.DEFAULT_CATALOG + // are taken into account later, at runtime, when rendering table/sequence names. + // These fields are exclusively about mapping defaults, + // overridden in XML mappings or through setters in MetadataBuilder. + this.implicitSchemaName = null; + this.implicitCatalogName = null; this.implicitlyQuoteIdentifiers = configService.getSetting( AvailableSettings.GLOBALLY_QUOTED_IDENTIFIERS, diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java index 704437008fc9..dfff98061cdd 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java @@ -82,7 +82,9 @@ import static org.hibernate.cfg.AvailableSettings.CRITERIA_LITERAL_HANDLING_MODE; import static org.hibernate.cfg.AvailableSettings.CUSTOM_ENTITY_DIRTINESS_STRATEGY; import static org.hibernate.cfg.AvailableSettings.DEFAULT_BATCH_FETCH_SIZE; +import static org.hibernate.cfg.AvailableSettings.DEFAULT_CATALOG; import static org.hibernate.cfg.AvailableSettings.DEFAULT_ENTITY_MODE; +import static org.hibernate.cfg.AvailableSettings.DEFAULT_SCHEMA; import static org.hibernate.cfg.AvailableSettings.DELAY_ENTITY_LOADER_CREATIONS; import static org.hibernate.cfg.AvailableSettings.ENABLE_LAZY_LOAD_NO_TRANS; import static org.hibernate.cfg.AvailableSettings.FAIL_ON_PAGINATION_OVER_COLLECTION_FETCH; @@ -242,6 +244,12 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions { private boolean queryParametersValidationEnabled; private LiteralHandlingMode criteriaLiteralHandlingMode; private ImmutableEntityUpdateQueryHandlingMode immutableEntityUpdateQueryHandlingMode; + // These two settings cannot be modified from the builder, + // in order to maintain consistency. + // Indeed, other components (the schema tools) also make use of these settings, + // and THOSE do not have access to session factory options. + private final String defaultCatalog; + private final String defaultSchema; private Map sqlFunctions; @@ -531,6 +539,9 @@ else if ( jdbcTimeZoneValue != null ) { configurationSettings.get( IMMUTABLE_ENTITY_UPDATE_QUERY_HANDLING_MODE ) ); + this.defaultCatalog = ConfigurationHelper.getString( DEFAULT_CATALOG, configurationSettings ); + this.defaultSchema = ConfigurationHelper.getString( DEFAULT_SCHEMA, configurationSettings ); + this.inClauseParameterPaddingEnabled = ConfigurationHelper.getBoolean( IN_CLAUSE_PARAMETER_PADDING, configurationSettings, @@ -1047,6 +1058,16 @@ public ImmutableEntityUpdateQueryHandlingMode getImmutableEntityUpdateQueryHandl return immutableEntityUpdateQueryHandlingMode; } + @Override + public String getDefaultCatalog() { + return defaultCatalog; + } + + @Override + public String getDefaultSchema() { + return defaultSchema; + } + @Override public boolean jdbcStyleParamsZeroBased() { return this.jdbcStyleParamsZeroBased; @@ -1092,7 +1113,6 @@ public boolean isOmitJoinOfSuperclassTablesEnabled() { return omitJoinOfSuperclassTablesEnabled; } - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // In-flight mutation access diff --git a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/stax/LocalXmlResourceResolver.java b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/stax/LocalXmlResourceResolver.java index 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/AbstractAuxiliaryDatabaseObject.java b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/AbstractAuxiliaryDatabaseObject.java index 95230353ed09..cf3eb2bb32b9 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/AbstractAuxiliaryDatabaseObject.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/AbstractAuxiliaryDatabaseObject.java @@ -13,7 +13,7 @@ import org.hibernate.dialect.Dialect; /** - * Convenience base class for {@link org.hibernate.mapping.AuxiliaryDatabaseObject}s. + * Convenience base class for {@link AuxiliaryDatabaseObject}s. *

      * This implementation performs dialect scoping checks strictly based on * dialect name comparisons. Custom implementations might want to do 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 7434c0772afd..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 @@ -39,14 +39,42 @@ public interface AuxiliaryDatabaseObject extends Exportable, Serializable { */ public boolean beforeTablesOnCreation(); + /** + * Gets the SQL strings for creating the database object. + * + * @param context A context to help generate the SQL creation strings + * + * @return the SQL strings for creating the database object. + */ + default String[] sqlCreateStrings(SqlStringGenerationContext context) { + return sqlCreateStrings( context.getDialect() ); + } + /** * Gets the SQL strings for creating the database object. * * @param dialect The dialect for which to generate the SQL creation strings * * @return the SQL strings for creating the database object. + * @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) { + throw new IllegalStateException( this + " does not implement sqlCreateStrings(...)" ); + } + + /** + * Gets the SQL strings for dropping the database object. + * + * @param context A context to help generate the SQL drop strings + * + * @return the SQL strings for dropping the database object. */ - public String[] sqlCreateStrings(Dialect dialect); + default String[] sqlDropStrings(SqlStringGenerationContext context) { + return sqlDropStrings( context.getDialect() ); + } /** * Gets the SQL strings for dropping the database object. @@ -54,8 +82,14 @@ public interface AuxiliaryDatabaseObject extends Exportable, Serializable { * @param dialect The dialect for which to generate the SQL drop strings * * @return the SQL strings for dropping the database object. + * @deprecated Hibernate ORM may never call this method, + * and implementations cannot properly handle default catalogs/schemas. + * Call/implement {@link #sqlDropStrings(SqlStringGenerationContext)} instead. */ - public String[] sqlDropStrings(Dialect dialect); + @Deprecated + default String[] sqlDropStrings(Dialect dialect) { + throw new IllegalStateException( this + " does not implement sqlDropStrings(...)" ); + } /** * Additional, optional interface for AuxiliaryDatabaseObject that want to allow diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/Database.java b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/Database.java index ef77cb757248..5cf503dad07e 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/Database.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/Database.java @@ -35,7 +35,7 @@ public class Database { private final ServiceRegistry serviceRegistry; private final PhysicalNamingStrategy physicalNamingStrategy; - private Namespace implicitNamespace; + private Namespace.Name physicalImplicitNamespaceName; private List initCommands; public Database(MetadataBuildingOptions buildingOptions) { @@ -48,11 +48,16 @@ public Database(MetadataBuildingOptions buildingOptions, JdbcEnvironment jdbcEnv this.physicalNamingStrategy = buildingOptions.getPhysicalNamingStrategy(); this.dialect = determineDialect( buildingOptions ); - this.implicitNamespace = makeNamespace( - new Namespace.Name( - toIdentifier( buildingOptions.getMappingDefaults().getImplicitCatalogName() ), - toIdentifier( buildingOptions.getMappingDefaults().getImplicitSchemaName() ) - ) + setImplicitNamespaceName( + toIdentifier( buildingOptions.getMappingDefaults().getImplicitCatalogName() ), + toIdentifier( buildingOptions.getMappingDefaults().getImplicitSchemaName() ) + ); + } + + private void setImplicitNamespaceName(Identifier catalogName, Identifier schemaName) { + this.physicalImplicitNamespaceName = new Namespace.Name( + physicalNamingStrategy.toPhysicalCatalogName( catalogName, jdbcEnvironment ), + physicalNamingStrategy.toPhysicalSchemaName( schemaName, jdbcEnvironment ) ); } @@ -108,15 +113,25 @@ public Iterable getNamespaces() { return namespaceMap.values(); } + /** + * @return The default namespace, with a {@code null} catalog and schema + * which will have to be interpreted with defaults at runtime. + * @see SqlStringGenerationContext + */ public Namespace getDefaultNamespace() { - return implicitNamespace; + return locateNamespace( null, null ); } - public Namespace locateNamespace(Identifier catalogName, Identifier schemaName) { - if ( catalogName == null && schemaName == null ) { - return getDefaultNamespace(); - } + /** + * @return The implicit name of the default namespace, with a {@code null} catalog and schema + * which will have to be interpreted with defaults at runtime. + * @see SqlStringGenerationContext + */ + public Namespace.Name getPhysicalImplicitNamespaceName() { + return physicalImplicitNamespaceName; + } + public Namespace locateNamespace(Identifier catalogName, Identifier schemaName) { final Namespace.Name name = new Namespace.Name( catalogName, schemaName ); Namespace namespace = namespaceMap.get( name ); if ( namespace == null ) { @@ -126,17 +141,8 @@ public Namespace locateNamespace(Identifier catalogName, Identifier schemaName) } public Namespace adjustDefaultNamespace(Identifier catalogName, Identifier schemaName) { - final Namespace.Name name = new Namespace.Name( catalogName, schemaName ); - if ( implicitNamespace.getName().equals( name ) ) { - return implicitNamespace; - } - - Namespace namespace = namespaceMap.get( name ); - if ( namespace == null ) { - namespace = makeNamespace( name ); - } - implicitNamespace = namespace; - return implicitNamespace; + setImplicitNamespaceName( catalogName, schemaName ); + return locateNamespace( catalogName, schemaName ); } public Namespace adjustDefaultNamespace(String implicitCatalogName, String implicitSchemaName) { diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/QualifiedName.java b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/QualifiedName.java index 92139e89c224..b7af552a2b2d 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/QualifiedName.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/QualifiedName.java @@ -31,8 +31,8 @@ public interface QualifiedName { * Returns a String-form of the qualified name. *

      * Depending on intention, may not be appropriate. May want - * {@link org.hibernate.engine.jdbc.env.spi.QualifiedObjectNameFormatter#format} - * instead. See {@link org.hibernate.engine.jdbc.env.spi.JdbcEnvironment#getQualifiedObjectNameFormatter} + * {@link SqlStringGenerationContext#format} + * instead. * * @return The string form */ 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}. + * This default is generally applied automatically by the {@link #format(QualifiedName) format methods}, + * but in some cases it can be useful to access it directly. + */ + Identifier getDefaultCatalog(); + + /** + * @param explicitCatalogOrNull An explicitly configured catalog, or {@code null}. + * @return The given identifier if non-{@code null}, or the default catalog otherwise. + */ + Identifier catalogWithDefault(Identifier explicitCatalogOrNull); + + /** + * @return The default schema, used for table/sequence names that do not explicitly mention a schema. + * May be {@code null}. + * This default is generally applied automatically by the {@link #format(QualifiedName) format methods}, + * but in some cases it can be useful to access it directly. + */ + Identifier getDefaultSchema(); + + /** + * @param explicitSchemaOrNull An explicitly configured schema, or {@code null}. + * @return The given identifier if non-{@code null}, or the default schema otherwise. + */ + Identifier schemaWithDefault(Identifier explicitSchemaOrNull); + + /** + * Render a formatted a table name + * + * @param qualifiedName The table name + * + * @return The formatted name, + */ + String format(QualifiedTableName qualifiedName); + + /** + * Render a formatted a table name, ignoring the default catalog/schema. + * + * @param qualifiedName The table name + * + * @return The formatted name + */ + String formatWithoutDefaults(QualifiedTableName qualifiedName); + + /** + * Render a formatted sequence name + * + * @param qualifiedName The sequence name + * + * @return The formatted name + */ + String format(QualifiedSequenceName qualifiedName); + + /** + * Render a formatted non-table and non-sequence qualified name + * + * @param qualifiedName The name + * + * @return The formatted name + */ + String format(QualifiedName qualifiedName); + + /** + * Render a formatted sequence name, without the catalog (even the default one). + * + * @param qualifiedName The sequence name + * + * @return The formatted name + */ + String formatWithoutCatalog(QualifiedSequenceName qualifiedName); + +} 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 new file mode 100644 index 000000000000..e61331500201 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/internal/SqlStringGenerationContextImpl.java @@ -0,0 +1,237 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 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.model.relational.internal; + +import java.sql.SQLException; +import java.util.Map; + +import org.hibernate.boot.model.naming.Identifier; +import org.hibernate.boot.model.relational.Database; +import org.hibernate.boot.model.relational.Namespace; +import org.hibernate.boot.model.relational.QualifiedName; +import org.hibernate.boot.model.relational.QualifiedSequenceName; +import org.hibernate.boot.model.relational.QualifiedTableName; +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. + * @param database The database metadata, to retrieve the implicit namespace name configured through XML mapping. + * @param configurationMap The configuration map, holding settings such as {@link AvailableSettings#DEFAULT_SCHEMA}. + * @return An {@link SqlStringGenerationContext}. + */ + public static SqlStringGenerationContext fromConfigurationMap(JdbcEnvironment jdbcEnvironment, + Database database, Map configurationMap) { + String defaultCatalog = (String) configurationMap.get( AvailableSettings.DEFAULT_CATALOG ); + String defaultSchema = (String) configurationMap.get( AvailableSettings.DEFAULT_SCHEMA ); + return fromExplicit( jdbcEnvironment, database, defaultCatalog, defaultSchema ); + } + + /** + * @param jdbcEnvironment The JDBC environment, to extract the dialect, identifier helper, etc. + * @param database The database metadata, to retrieve the implicit namespace name configured through XML mapping. + * @param defaultCatalog The default catalog to use; if {@code null}, will use the implicit catalog that was configured through XML mapping. + * @param defaultSchema The default schema to use; if {@code null}, will use the implicit schema that was configured through XML mapping. + * @return An {@link SqlStringGenerationContext}. + */ + public static SqlStringGenerationContext fromExplicit(JdbcEnvironment jdbcEnvironment, + Database database, String defaultCatalog, String defaultSchema) { + Namespace.Name implicitNamespaceName = database.getPhysicalImplicitNamespaceName(); + IdentifierHelper identifierHelper = jdbcEnvironment.getIdentifierHelper(); + NameQualifierSupport nameQualifierSupport = jdbcEnvironment.getNameQualifierSupport(); + Identifier actualDefaultCatalog = null; + if ( nameQualifierSupport.supportsCatalogs() ) { + actualDefaultCatalog = identifierHelper.toIdentifier( defaultCatalog ); + if ( actualDefaultCatalog == null ) { + actualDefaultCatalog = implicitNamespaceName.getCatalog(); + } + } + Identifier actualDefaultSchema = null; + if ( nameQualifierSupport.supportsSchemas() ) { + actualDefaultSchema = identifierHelper.toIdentifier( defaultSchema ); + if ( defaultSchema == null ) { + actualDefaultSchema = implicitNamespaceName.getSchema(); + } + } + 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 ); + } + + public static SqlStringGenerationContext forTests(JdbcEnvironment jdbcEnvironment, + String defaultCatalog, String defaultSchema) { + IdentifierHelper identifierHelper = jdbcEnvironment.getIdentifierHelper(); + return new SqlStringGenerationContextImpl( jdbcEnvironment, + identifierHelper.toIdentifier( defaultCatalog ), identifierHelper.toIdentifier( defaultSchema ) ); + } + + private final Dialect dialect; + private final IdentifierHelper identifierHelper; + private final QualifiedObjectNameFormatter qualifiedObjectNameFormatter; + private final Identifier defaultCatalog; + private final Identifier defaultSchema; + + @SuppressWarnings("deprecation") + private SqlStringGenerationContextImpl(JdbcEnvironment jdbcEnvironment, + Identifier defaultCatalog, Identifier defaultSchema) { + 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; + } + + @Override + public Dialect getDialect() { + return dialect; + } + + @Override + 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; + } + + @Override + public Identifier catalogWithDefault(Identifier explicitCatalogOrNull) { + return explicitCatalogOrNull != null ? explicitCatalogOrNull : defaultCatalog; + } + + @Override + public Identifier getDefaultSchema() { + return defaultSchema; + } + + @Override + public Identifier schemaWithDefault(Identifier explicitSchemaOrNull) { + return explicitSchemaOrNull != null ? explicitSchemaOrNull : defaultSchema; + } + + private QualifiedTableName withDefaults(QualifiedTableName name) { + if ( name.getCatalogName() == null && defaultCatalog != null + || name.getSchemaName() == null && defaultSchema != null ) { + return new QualifiedTableName( catalogWithDefault( name.getCatalogName() ), + schemaWithDefault( name.getSchemaName() ), name.getTableName() ); + } + return name; + } + + private QualifiedSequenceName withDefaults(QualifiedSequenceName name) { + if ( name.getCatalogName() == null && defaultCatalog != null + || name.getSchemaName() == null && defaultSchema != null ) { + return new QualifiedSequenceName( catalogWithDefault( name.getCatalogName() ), + schemaWithDefault( name.getSchemaName() ), name.getSequenceName() ); + } + return name; + } + + private QualifiedName withDefaults(QualifiedName name) { + if ( name.getCatalogName() == null && defaultCatalog != null + || name.getSchemaName() == null && defaultSchema != null ) { + return new QualifiedSequenceName( catalogWithDefault( name.getCatalogName() ), + schemaWithDefault( name.getSchemaName() ), name.getObjectName() ); + } + return name; + } + + @Override + public String format(QualifiedTableName qualifiedName) { + return qualifiedObjectNameFormatter.format( withDefaults( qualifiedName ), dialect ); + } + + @Override + public String formatWithoutDefaults(QualifiedTableName qualifiedName) { + return qualifiedObjectNameFormatter.format( qualifiedName, dialect ); + } + + @Override + public String format(QualifiedSequenceName qualifiedName) { + return qualifiedObjectNameFormatter.format( withDefaults( qualifiedName ), dialect ); + } + + @Override + public String format(QualifiedName qualifiedName) { + return qualifiedObjectNameFormatter.format( withDefaults( qualifiedName ), dialect ); + } + + @Override + public String formatWithoutCatalog(QualifiedSequenceName qualifiedName) { + QualifiedSequenceName nameToFormat; + if ( qualifiedName.getCatalogName() != null + || qualifiedName.getSchemaName() == null && defaultSchema != null ) { + nameToFormat = new QualifiedSequenceName( null, + schemaWithDefault( qualifiedName.getSchemaName() ), qualifiedName.getSequenceName() ); + } + else { + nameToFormat = qualifiedName; + } + return qualifiedObjectNameFormatter.format( nameToFormat, dialect ); + } +} 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 3efebf2d3f62..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 @@ -804,19 +804,6 @@ private void makeIdentifier( // YUCK! but cannot think of a clean way to do this given the string-config based scheme params.put( PersistentIdentifierGenerator.IDENTIFIER_NORMALIZER, objectNameNormalizer); - if ( database.getDefaultNamespace().getPhysicalName().getSchema() != null ) { - params.setProperty( - PersistentIdentifierGenerator.SCHEMA, - database.getDefaultNamespace().getPhysicalName().getSchema().render( database.getDialect() ) - ); - } - if ( database.getDefaultNamespace().getPhysicalName().getCatalog() != null ) { - params.setProperty( - PersistentIdentifierGenerator.CATALOG, - database.getDefaultNamespace().getPhysicalName().getCatalog().render( database.getDialect() ) - ); - } - params.putAll( generator.getParameters() ); identifierValue.setIdentifierGeneratorProperties( params ); @@ -2961,7 +2948,7 @@ private Identifier determineCatalogName(TableSpecificationSource tableSpecSource return database.toIdentifier( tableSpecSource.getExplicitCatalogName() ); } else { - return database.getDefaultNamespace().getName().getCatalog(); + return null; } } @@ -2970,7 +2957,7 @@ private Identifier determineSchemaName(TableSpecificationSource tableSpecSource) return database.toIdentifier( tableSpecSource.getExplicitSchemaName() ); } else { - return database.getDefaultNamespace().getName().getSchema(); + return null; } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/source/spi/MetadataSourceProcessor.java b/hibernate-core/src/main/java/org/hibernate/boot/model/source/spi/MetadataSourceProcessor.java index 4a857b8a7848..d5e651d2ff06 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/source/spi/MetadataSourceProcessor.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/source/spi/MetadataSourceProcessor.java @@ -47,7 +47,7 @@ public interface MetadataSourceProcessor { void processNamedQueries(); /** - * Process all {@link org.hibernate.mapping.AuxiliaryDatabaseObject} definitions. + * Process all {@link org.hibernate.boot.model.relational.AuxiliaryDatabaseObject} definitions. *

      * This step has no prerequisites. */ diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java index 1ac730e0e52c..84953eac45ab 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java @@ -423,6 +423,16 @@ public ImmutableEntityUpdateQueryHandlingMode getImmutableEntityUpdateQueryHandl return delegate.getImmutableEntityUpdateQueryHandlingMode(); } + @Override + public String getDefaultCatalog() { + return delegate.getDefaultCatalog(); + } + + @Override + public String getDefaultSchema() { + return delegate.getDefaultSchema(); + } + @Override public boolean inClauseParameterPaddingEnabled() { return delegate.inClauseParameterPaddingEnabled(); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java index 93cb7516128e..30b3a761c2c1 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java @@ -293,6 +293,26 @@ default ImmutableEntityUpdateQueryHandlingMode getImmutableEntityUpdateQueryHand return ImmutableEntityUpdateQueryHandlingMode.WARNING; } + /** + * The default catalog to use in generated SQL when a catalog wasn't specified in the mapping, + * neither explicitly nor implicitly (see the concept of implicit catalog in XML mapping). + * + * @return The default catalog to use. + */ + default String getDefaultCatalog() { + return null; + } + + /** + * The default schema to use in generated SQL when a catalog wasn't specified in the mapping, + * neither explicitly nor implicitly (see the concept of implicit schema in XML mapping). + * + * @return The default schema to use. + */ + default String getDefaultSchema() { + return null; + } + default boolean inClauseParameterPaddingEnabled() { return false; } 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 786acc6b1296..49a059e61628 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java @@ -149,7 +149,6 @@ import org.hibernate.cfg.internal.NullableDiscriminatorColumnSecondPass; import org.hibernate.engine.OptimisticLockStyle; import org.hibernate.engine.spi.FilterDefinition; -import org.hibernate.id.PersistentIdentifierGenerator; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.StringHelper; import org.hibernate.jpa.event.internal.CallbackDefinitionResolverLegacyImpl; @@ -171,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; /** @@ -467,20 +467,6 @@ private static IdentifierGeneratorDefinition buildIdGenerator( IdentifierGeneratorDefinition.Builder definitionBuilder = new IdentifierGeneratorDefinition.Builder(); - if ( context.getMappingDefaults().getImplicitSchemaName() != null ) { - definitionBuilder.addParam( - PersistentIdentifierGenerator.SCHEMA, - context.getMappingDefaults().getImplicitSchemaName() - ); - } - - if ( context.getMappingDefaults().getImplicitCatalogName() != null ) { - definitionBuilder.addParam( - PersistentIdentifierGenerator.CATALOG, - context.getMappingDefaults().getImplicitCatalogName() - ); - } - if ( generatorAnn instanceof TableGenerator ) { context.getBuildingOptions().getIdGenerationTypeInterpreter().interpretTableGenerator( (TableGenerator) generatorAnn, @@ -706,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 { @@ -1386,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() @@ -1394,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() ); } @@ -1809,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 ); @@ -1828,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, @@ -1864,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 @@ -1874,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 ) { @@ -1893,7 +1883,8 @@ else if ( property.isAnnotationPresent( OneToOne.class ) ) { joinColumns, !mandatory, getFetchMode( ann.fetch() ), - ignoreNotFound, onDeleteCascade, + notFoundAction, + onDeleteCascade, ToOneBinder.getTargetEntity( inferredData, context ), propertyHolder, inferredData, @@ -2587,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 ); @@ -3055,7 +3046,7 @@ private static void bindManyToOne( String cascadeStrategy, Ejb3JoinColumn[] columns, boolean optional, - boolean ignoreNotFound, + NotFoundAction notFoundAction, boolean cascadeOnDelete, XClass targetEntity, PropertyHolder propertyHolder, @@ -3075,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 ) { @@ -3105,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; @@ -3168,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; } } @@ -3181,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(); @@ -3193,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 ) ); } @@ -3202,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 ); @@ -3228,7 +3240,7 @@ private static void bindOneToOne( Ejb3JoinColumn[] joinColumns, boolean optional, FetchMode fetchMode, - boolean ignoreNotFound, + NotFoundAction notFoundAction, boolean cascadeOnDelete, XClass targetEntity, PropertyHolder propertyHolder, @@ -3272,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( @@ -3282,7 +3294,7 @@ private static void bindOneToOne( propertyHolder, inferredData, targetEntity, - ignoreNotFound, + notFoundAction, cascadeOnDelete, optional, cascadeStrategy, @@ -3293,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 ); } } @@ -3477,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 { @@ -3639,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/BinderHelper.java b/hibernate-core/src/main/java/org/hibernate/cfg/BinderHelper.java index 4dd9882d5a17..b30c2180c54f 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/BinderHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/BinderHelper.java @@ -525,15 +525,6 @@ public static void makeIdGenerator( PersistentIdentifierGenerator.TABLE, table.getName() ); - final String implicitCatalogName = buildingContext.getBuildingOptions().getMappingDefaults().getImplicitCatalogName(); - if ( implicitCatalogName != null ) { - params.put( PersistentIdentifierGenerator.CATALOG, implicitCatalogName ); - } - final String implicitSchemaName = buildingContext.getBuildingOptions().getMappingDefaults().getImplicitSchemaName(); - if ( implicitSchemaName != null ) { - params.put( PersistentIdentifierGenerator.SCHEMA, implicitSchemaName ); - } - if ( id.getColumnSpan() == 1 ) { params.setProperty( PersistentIdentifierGenerator.PK, 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/Settings.java b/hibernate-core/src/main/java/org/hibernate/cfg/Settings.java index 244835656fce..ad9c46ad38e9 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/Settings.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/Settings.java @@ -50,8 +50,8 @@ public Settings(SessionFactoryOptions sessionFactoryOptions) { public Settings(SessionFactoryOptions sessionFactoryOptions, Metadata metadata) { this( sessionFactoryOptions, - extractName( metadata.getDatabase().getDefaultNamespace().getName().getCatalog() ), - extractName( metadata.getDatabase().getDefaultNamespace().getName().getSchema() ) + extractName( metadata.getDatabase().getPhysicalImplicitNamespaceName().getCatalog() ), + extractName( metadata.getDatabase().getPhysicalImplicitNamespaceName().getSchema() ) ); } @@ -61,8 +61,8 @@ private static String extractName(Identifier identifier) { public Settings(SessionFactoryOptions sessionFactoryOptions, String defaultCatalogName, String defaultSchemaName) { this.sessionFactoryOptions = sessionFactoryOptions; - this.defaultCatalogName = defaultCatalogName; - this.defaultSchemaName = defaultSchemaName; + this.defaultCatalogName = sessionFactoryOptions.getDefaultCatalog() != null ? sessionFactoryOptions.getDefaultCatalog() : defaultCatalogName; + this.defaultSchemaName = sessionFactoryOptions.getDefaultSchema() != null ? sessionFactoryOptions.getDefaultSchema() : defaultSchemaName; if ( LOG.isDebugEnabled() ) { LOG.debugf( "SessionFactory name : %s", sessionFactoryOptions.getSessionFactoryName() ); 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/TableBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/TableBinder.java index 2e5995f45647..7b9f7c5eff7b 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/TableBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/TableBinder.java @@ -477,10 +477,10 @@ public static Table buildAndFillTable( String subselect, InFlightMetadataCollector.EntityTableXref denormalizedSuperTableXref) { schema = BinderHelper.isEmptyOrNullAnnotationValue( schema ) - ? extract( buildingContext.getMetadataCollector().getDatabase().getDefaultNamespace().getPhysicalName().getSchema() ) + ? null : schema; catalog = BinderHelper.isEmptyOrNullAnnotationValue( catalog ) - ? extract( buildingContext.getMetadataCollector().getDatabase().getDefaultNamespace().getPhysicalName().getCatalog() ) + ? null : catalog; final Table table; 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 8a8c3167522c..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; @@ -407,7 +406,9 @@ public Annotation[] getAnnotations() { */ private void initAnnotations() { if ( annotations == null ) { - XMLContext.Default defaults = xmlContext.getDefault( className ); + // We don't want the global catalog and schema here: they are applied much later, + // when SQL gets rendered. + XMLContext.Default defaults = xmlContext.getDefaultWithoutGlobalCatalogAndSchema( className ); if ( className != null && propertyName == null ) { //is a class ManagedType managedTypeOverride = xmlContext.getManagedTypeOverride( className ); 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 fef5c77f0b3f..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(); @@ -94,7 +94,7 @@ public Map getDefaults() { else { if ( defaults == null ) { defaults = new HashMap<>(); - XMLContext.Default xmlDefaults = xmlContext.getDefault( null ); + XMLContext.Default xmlDefaults = xmlContext.getDefaultWithGlobalCatalogAndSchema(); defaults.put( "schema", xmlDefaults.getSchema() ); defaults.put( "catalog", xmlDefaults.getCatalog() ); 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/cfg/annotations/reflection/internal/XMLContext.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/internal/XMLContext.java index 06f1da27f913..c43223900ead 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/internal/XMLContext.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/internal/XMLContext.java @@ -124,12 +124,12 @@ private void addClass(List managedTypes, String packageNa managedTypeOverride.put( className, element ); Default mergedDefaults = new Default(); // Apply entity mapping defaults - mergedDefaults.override( defaults ); + mergedDefaults.overrideWithCatalogAndSchema( defaults ); // ... then apply entity settings Default fileDefaults = new Default(); fileDefaults.setMetadataComplete( element.isMetadataComplete() ); fileDefaults.setAccess( element.getAccess() ); - mergedDefaults.override( fileDefaults ); + mergedDefaults.overrideWithCatalogAndSchema( fileDefaults ); // ... and we get the merged defaults for that entity defaultsOverride.put( className, mergedDefaults ); @@ -196,16 +196,22 @@ public static String buildSafeClassName(String className, Default defaults) { return buildSafeClassName( className, defaults.getPackageName() ); } - public Default getDefault(String className) { + public Default getDefaultWithoutGlobalCatalogAndSchema(String className) { Default xmlDefault = new Default(); - xmlDefault.override( globalDefaults ); + xmlDefault.overrideWithoutCatalogAndSchema( globalDefaults ); if ( className != null ) { Default entityMappingOverriding = defaultsOverride.get( className ); - xmlDefault.override( entityMappingOverriding ); + xmlDefault.overrideWithCatalogAndSchema( entityMappingOverriding ); } return xmlDefault; } + public Default getDefaultWithGlobalCatalogAndSchema() { + Default xmlDefault = new Default(); + xmlDefault.overrideWithCatalogAndSchema( globalDefaults ); + return xmlDefault; + } + public ManagedType getManagedTypeOverride(String className) { return managedTypeOverride.get( className ); } @@ -292,7 +298,19 @@ void setCascadePersist(Boolean cascadePersist) { this.cascadePersist = cascadePersist; } - public void override(Default globalDefault) { + public void overrideWithCatalogAndSchema(Default override) { + overrideWithoutCatalogAndSchema( override ); + if ( override != null ) { + if ( override.getSchema() != null ) { + schema = override.getSchema(); + } + if ( override.getCatalog() != null ) { + catalog = override.getCatalog(); + } + } + } + + public void overrideWithoutCatalogAndSchema(Default globalDefault) { if ( globalDefault != null ) { if ( globalDefault.getAccess() != null ) { access = globalDefault.getAccess(); @@ -300,12 +318,6 @@ public void override(Default globalDefault) { if ( globalDefault.getPackageName() != null ) { packageName = globalDefault.getPackageName(); } - if ( globalDefault.getSchema() != null ) { - schema = globalDefault.getSchema(); - } - if ( globalDefault.getCatalog() != null ) { - catalog = globalDefault.getCatalog(); - } if ( globalDefault.getDelimitedIdentifier() != null ) { delimitedIdentifier = globalDefault.getDelimitedIdentifier(); } 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/AbstractHANADialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/AbstractHANADialect.java index 2c087e2ca3eb..61670a4fa4e5 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/AbstractHANADialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/AbstractHANADialect.java @@ -40,8 +40,10 @@ import org.hibernate.LockOptions; import org.hibernate.MappingException; import org.hibernate.ScrollMode; +import org.hibernate.boot.Metadata; import org.hibernate.boot.model.TypeContributions; import org.hibernate.boot.model.naming.Identifier; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.cfg.AvailableSettings; import org.hibernate.dialect.function.AnsiTrimFunction; import org.hibernate.dialect.function.NoArgSQLFunction; @@ -728,14 +730,16 @@ public int getMaxLobPrefetchSize() { private final StandardTableExporter hanaTableExporter = new StandardTableExporter( this ) { @Override - public String[] getSqlCreateStrings(org.hibernate.mapping.Table table, org.hibernate.boot.Metadata metadata) { - String[] sqlCreateStrings = super.getSqlCreateStrings( table, metadata ); + public String[] getSqlCreateStrings(Table table, Metadata metadata, + SqlStringGenerationContext context) { + String[] sqlCreateStrings = super.getSqlCreateStrings( table, metadata, context ); return quoteTypeIfNecessary( table, sqlCreateStrings, getCreateTableString() ); } @Override - public String[] getSqlDropStrings(Table table, org.hibernate.boot.Metadata metadata) { - String[] sqlDropStrings = super.getSqlDropStrings( table, metadata ); + public String[] getSqlDropStrings(Table table, Metadata metadata, + SqlStringGenerationContext context) { + String[] sqlDropStrings = super.getSqlDropStrings( table, metadata, context ); return quoteTypeIfNecessary( table, sqlDropStrings, "drop table" ); } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java index d157f8c59988..3d4a4b243141 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java @@ -2953,6 +2953,10 @@ public boolean isJdbcLogWarningsEnabledByDefault() { return true; } + public void augmentPhysicalTableTypes(List tableTypesList) { + // nothing to do + } + public void augmentRecognizedTableTypes(List tableTypesList) { // noihing to do } 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 319b64943d9d..5b1dff81e203 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java @@ -8,6 +8,7 @@ import java.sql.SQLException; import java.sql.Types; +import java.util.List; import org.hibernate.JDBCException; import org.hibernate.PessimisticLockException; @@ -15,9 +16,11 @@ import org.hibernate.cfg.AvailableSettings; import org.hibernate.dialect.function.AvgWithArgumentCastFunction; import org.hibernate.dialect.function.NoArgSQLFunction; +import org.hibernate.dialect.function.SQLFunctionTemplate; 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; @@ -74,6 +77,8 @@ public boolean bindLimitParametersInReverseOrder() { }; private final boolean supportsTuplesInSubqueries; + private final boolean hasOddDstBehavior; + private final boolean isVersion2; private final String querySequenceString; private final SequenceInformationExtractor sequenceInformationExtractor; @@ -85,6 +90,8 @@ public H2Dialect() { int buildId = Integer.MIN_VALUE; boolean supportsTuplesInSubqueries = false; + boolean hasOddDstBehavior = false; + boolean isVersion2 = false; try { // HHH-2300 @@ -97,6 +104,9 @@ public H2Dialect() { LOG.unsupportedMultiTableBulkHqlJpaql( majorVersion, minorVersion, buildId ); } supportsTuplesInSubqueries = majorVersion > 1 || minorVersion > 4 || buildId >= 198; + // As of 1.4.200, the DST handling for timestamp without time zone is odd + hasOddDstBehavior = majorVersion > 1 || minorVersion > 4 || buildId >= 200; + isVersion2 = majorVersion > 1; } catch ( Exception e ) { // probably H2 not in the classpath, though in certain app server environments it might just mean we are @@ -115,6 +125,8 @@ public H2Dialect() { this.querySequenceString = null; } this.supportsTuplesInSubqueries = supportsTuplesInSubqueries; + this.hasOddDstBehavior = hasOddDstBehavior; + this.isVersion2 = isVersion2; registerColumnType( Types.BOOLEAN, "boolean" ); registerColumnType( Types.BIGINT, "bigint" ); @@ -140,6 +152,12 @@ public H2Dialect() { registerColumnType( Types.BLOB, "blob" ); registerColumnType( Types.CLOB, "clob" ); + if ( isVersion2 ) { + registerColumnType( Types.LONGVARCHAR, "character varying" ); + registerColumnType( Types.BINARY, "binary($l)" ); + registerFunction( "str", new SQLFunctionTemplate( StandardBasicTypes.STRING, "cast(?1 as character varying)") ); + } + // Aggregations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ registerFunction( "avg", new AvgWithArgumentCastFunction( "double" ) ); @@ -235,6 +253,15 @@ public H2Dialect() { getDefaultProperties().setProperty( AvailableSettings.NON_CONTEXTUAL_LOB_CREATION, "true" ); } + public boolean hasOddDstBehavior() { + // H2 1.4.200 has a bug: https://github.com/h2database/h2database/issues/3184 + return hasOddDstBehavior; + } + + public boolean isVersion2() { + return isVersion2; + } + @Override public String getAddColumnString() { return "add column"; @@ -245,6 +272,11 @@ public String getForUpdateString() { return " for update"; } + @Override + public String toBooleanValueString(boolean bool) { + return String.valueOf( bool ); + } + @Override public LimitHandler getLimitHandler() { return LIMIT_HANDLER; @@ -422,6 +454,13 @@ public boolean supportsUnionAll() { // Overridden informational metadata ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + @Override + public void augmentPhysicalTableTypes(List tableTypesList) { + if ( isVersion2 ) { + tableTypesList.add( "BASE TABLE" ); + } + } + @Override public boolean supportsLobValueChangePropogation() { return false; @@ -467,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/OracleTypesHelper.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleTypesHelper.java index 4b757ab4385a..8c18edd73978 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleTypesHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleTypesHelper.java @@ -36,7 +36,7 @@ private OracleTypesHelper() { typeCode = extractOracleCursorTypeValue(); } catch (Exception e) { - log.warn( "Unable to resolve Oracle CURSOR JDBC type code", e ); + log.warn( "Unable to resolve Oracle CURSOR JDBC type code: the class OracleTypesHelper was initialized but the Oracle JDBC driver could not be loaded." ); } oracleCursorTypeSqlType = typeCode; } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQL81Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQL81Dialect.java index ad4ee3a3eef4..09b8df5cb52a 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQL81Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQL81Dialect.java @@ -114,7 +114,7 @@ public PostgreSQL81Dialect() { registerColumnType( Types.BINARY, "bytea" ); registerColumnType( Types.LONGVARCHAR, "text" ); registerColumnType( Types.LONGVARBINARY, "bytea" ); - registerColumnType( Types.CLOB, "text" ); + registerColumnType( Types.CLOB, "oid" ); registerColumnType( Types.BLOB, "oid" ); registerColumnType( Types.NUMERIC, "numeric($p, $s)" ); registerColumnType( Types.OTHER, "uuid" ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SQLServer2012Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/SQLServer2012Dialect.java index b25672486650..1818f5042ba9 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SQLServer2012Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SQLServer2012Dialect.java @@ -9,9 +9,9 @@ import org.hibernate.boot.Metadata; import org.hibernate.boot.model.relational.QualifiedSequenceName; import org.hibernate.boot.model.relational.Sequence; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.dialect.pagination.LimitHandler; import org.hibernate.dialect.pagination.SQLServer2012LimitHandler; -import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.tool.schema.internal.StandardSequenceExporter; import org.hibernate.tool.schema.spi.Exporter; @@ -118,14 +118,12 @@ public SqlServerSequenceExporter(Dialect dialect) { } @Override - protected String getFormattedSequenceName(QualifiedSequenceName name, Metadata metadata) { - if ( name.getCatalogName() != null ) { - // SQL Server does not allow the catalog in the sequence name. - // See https://docs.microsoft.com/en-us/sql/t-sql/statements/create-sequence-transact-sql?view=sql-server-ver15&viewFallbackFrom=sql-server-ver12 - // Keeping the catalog in the name does not break on ORM, but it fails using Vert.X for Reactive. - name = new QualifiedSequenceName( null, name.getSchemaName(), name.getObjectName() ); - } - return super.getFormattedSequenceName( name, metadata ); + protected String getFormattedSequenceName(QualifiedSequenceName name, Metadata metadata, + SqlStringGenerationContext context) { + // SQL Server does not allow the catalog in the sequence name. + // See https://docs.microsoft.com/en-us/sql/t-sql/statements/create-sequence-transact-sql?view=sql-server-ver15&viewFallbackFrom=sql-server-ver12 + // Keeping the catalog in the name does not break on ORM, but it fails using Vert.X for Reactive. + return context.formatWithoutCatalog( name ); } } } 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/Teradata14Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/Teradata14Dialect.java index 9744036649a6..1526bbb975e5 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/Teradata14Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/Teradata14Dialect.java @@ -17,6 +17,7 @@ import org.hibernate.LockOptions; import org.hibernate.boot.Metadata; import org.hibernate.boot.model.relational.QualifiedNameImpl; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.cfg.Environment; import org.hibernate.dialect.function.SQLFunctionTemplate; import org.hibernate.dialect.identity.IdentityColumnSupport; @@ -214,22 +215,19 @@ public TeradataIndexExporter(Dialect dialect) { } @Override - public String[] getSqlCreateStrings(Index index, Metadata metadata) { + public String[] getSqlCreateStrings(Index index, Metadata metadata, + SqlStringGenerationContext context) { final JdbcEnvironment jdbcEnvironment = metadata.getDatabase().getJdbcEnvironment(); - final String tableName = jdbcEnvironment.getQualifiedObjectNameFormatter().format( - index.getTable().getQualifiedTableName(), - jdbcEnvironment.getDialect() - ); + final String tableName = context.format( index.getTable().getQualifiedTableName() ); final String indexNameForCreation; if ( getDialect().qualifyIndexName() ) { - indexNameForCreation = jdbcEnvironment.getQualifiedObjectNameFormatter().format( + indexNameForCreation = context.format( new QualifiedNameImpl( index.getTable().getQualifiedTableName().getCatalogName(), index.getTable().getQualifiedTableName().getSchemaName(), jdbcEnvironment.getIdentifierHelper().toIdentifier( index.getName() ) - ), - jdbcEnvironment.getDialect() + ) ); } else { 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/GetGeneratedKeysDelegate.java b/hibernate-core/src/main/java/org/hibernate/dialect/identity/GetGeneratedKeysDelegate.java index 38cc8e323c47..ef4c6e389640 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/identity/GetGeneratedKeysDelegate.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/identity/GetGeneratedKeysDelegate.java @@ -11,6 +11,7 @@ import java.sql.ResultSet; import java.sql.SQLException; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.dialect.Dialect; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.id.IdentifierGeneratorHelper; @@ -37,7 +38,7 @@ public GetGeneratedKeysDelegate(PostInsertIdentityPersister persister, Dialect d } @Override - public IdentifierGeneratingInsert prepareIdentifierGeneratingInsert() { + public IdentifierGeneratingInsert prepareIdentifierGeneratingInsert(SqlStringGenerationContext context) { IdentifierGeneratingInsert insert = new IdentifierGeneratingInsert( dialect ); insert.addIdentityColumn( persister.getRootTableKeyColumnNames()[0] ); return insert; 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 a114319e98d8..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; @@ -28,6 +34,6 @@ public String getIdentitySelectString(String table, String column, int type) { @Override public String getIdentityInsertString() { - return "null"; + return "default"; } } 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/DB2UniqueDelegate.java b/hibernate-core/src/main/java/org/hibernate/dialect/unique/DB2UniqueDelegate.java index b866eb762b95..1770053ab807 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/unique/DB2UniqueDelegate.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/unique/DB2UniqueDelegate.java @@ -9,6 +9,7 @@ import java.util.Iterator; import org.hibernate.boot.Metadata; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.dialect.Dialect; import org.hibernate.mapping.UniqueKey; @@ -29,10 +30,11 @@ public DB2UniqueDelegate( Dialect dialect ) { } @Override - public String getAlterTableToAddUniqueKeyCommand(UniqueKey uniqueKey, Metadata metadata) { + public String getAlterTableToAddUniqueKeyCommand(UniqueKey uniqueKey, Metadata metadata, + SqlStringGenerationContext context) { if ( hasNullable( uniqueKey ) ) { return org.hibernate.mapping.Index.buildSqlCreateIndexString( - dialect, + context, uniqueKey.getName(), uniqueKey.getTable(), uniqueKey.columnIterator(), @@ -42,23 +44,21 @@ public String getAlterTableToAddUniqueKeyCommand(UniqueKey uniqueKey, Metadata m ); } else { - return super.getAlterTableToAddUniqueKeyCommand( uniqueKey, metadata ); + return super.getAlterTableToAddUniqueKeyCommand( uniqueKey, metadata, context ); } } @Override - public String getAlterTableToDropUniqueKeyCommand(UniqueKey uniqueKey, Metadata metadata) { + public String getAlterTableToDropUniqueKeyCommand(UniqueKey uniqueKey, Metadata metadata, + SqlStringGenerationContext context) { if ( hasNullable( uniqueKey ) ) { return org.hibernate.mapping.Index.buildSqlDropIndexString( uniqueKey.getName(), - metadata.getDatabase().getJdbcEnvironment().getQualifiedObjectNameFormatter().format( - uniqueKey.getTable().getQualifiedTableName(), - metadata.getDatabase().getJdbcEnvironment().getDialect() - ) + context.format( uniqueKey.getTable().getQualifiedTableName() ) ); } else { - return super.getAlterTableToDropUniqueKeyCommand( uniqueKey, metadata ); + return super.getAlterTableToDropUniqueKeyCommand( uniqueKey, metadata, context ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/unique/DefaultUniqueDelegate.java b/hibernate-core/src/main/java/org/hibernate/dialect/unique/DefaultUniqueDelegate.java index eec4fbd62f1f..f73d3ca30db3 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/unique/DefaultUniqueDelegate.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/unique/DefaultUniqueDelegate.java @@ -9,8 +9,10 @@ import java.util.Iterator; import org.hibernate.boot.Metadata; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.dialect.Dialect; -import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; +import org.hibernate.mapping.Column; +import org.hibernate.mapping.Table; import org.hibernate.mapping.UniqueKey; /** @@ -34,23 +36,21 @@ public DefaultUniqueDelegate( Dialect dialect ) { // legacy model ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @Override - public String getColumnDefinitionUniquenessFragment(org.hibernate.mapping.Column column) { + public String getColumnDefinitionUniquenessFragment(Column column, + SqlStringGenerationContext context) { return ""; } @Override - public String getTableCreationUniqueConstraintsFragment(org.hibernate.mapping.Table table) { + public String getTableCreationUniqueConstraintsFragment(Table table, + SqlStringGenerationContext context) { return ""; } @Override - public String getAlterTableToAddUniqueKeyCommand(UniqueKey uniqueKey, Metadata metadata) { - final JdbcEnvironment jdbcEnvironment = metadata.getDatabase().getJdbcEnvironment(); - - final String tableName = jdbcEnvironment.getQualifiedObjectNameFormatter().format( - uniqueKey.getTable().getQualifiedTableName(), - dialect - ); + public String getAlterTableToAddUniqueKeyCommand(UniqueKey uniqueKey, Metadata metadata, + SqlStringGenerationContext context) { + final String tableName = context.format( uniqueKey.getTable().getQualifiedTableName() ); final String constraintName = dialect.quote( uniqueKey.getName() ); return dialect.getAlterTableString( tableName ) @@ -76,13 +76,9 @@ protected String uniqueConstraintSql(UniqueKey uniqueKey) { } @Override - public String getAlterTableToDropUniqueKeyCommand(UniqueKey uniqueKey, Metadata metadata) { - final JdbcEnvironment jdbcEnvironment = metadata.getDatabase().getJdbcEnvironment(); - - final String tableName = jdbcEnvironment.getQualifiedObjectNameFormatter().format( - uniqueKey.getTable().getQualifiedTableName(), - dialect - ); + public String getAlterTableToDropUniqueKeyCommand(UniqueKey uniqueKey, Metadata metadata, + SqlStringGenerationContext context) { + final String tableName = context.format( uniqueKey.getTable().getQualifiedTableName() ); final StringBuilder buf = new StringBuilder( dialect.getAlterTableString(tableName) ); buf.append( getDropUnique() ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/unique/InformixUniqueDelegate.java b/hibernate-core/src/main/java/org/hibernate/dialect/unique/InformixUniqueDelegate.java index ae8fc4204cac..cffa7b5ffe82 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/unique/InformixUniqueDelegate.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/unique/InformixUniqueDelegate.java @@ -7,6 +7,7 @@ package org.hibernate.dialect.unique; import org.hibernate.boot.Metadata; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.dialect.Dialect; import org.hibernate.mapping.UniqueKey; @@ -24,13 +25,11 @@ public InformixUniqueDelegate( Dialect dialect ) { // legacy model ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @Override - public String getAlterTableToAddUniqueKeyCommand(UniqueKey uniqueKey, Metadata metadata) { + public String getAlterTableToAddUniqueKeyCommand(UniqueKey uniqueKey, Metadata metadata, + SqlStringGenerationContext context) { // Do this here, rather than allowing UniqueKey/Constraint to do it. // We need full, simplified control over whether or not it happens. - final String tableName = metadata.getDatabase().getJdbcEnvironment().getQualifiedObjectNameFormatter().format( - uniqueKey.getTable().getQualifiedTableName(), - metadata.getDatabase().getJdbcEnvironment().getDialect() - ); + final String tableName = context.format( uniqueKey.getTable().getQualifiedTableName() ); final String constraintName = dialect.quote( uniqueKey.getName() ); return dialect.getAlterTableString( tableName ) + " add constraint " + uniqueConstraintSql( uniqueKey ) + " constraint " + constraintName; 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 89ef372eba2c..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 @@ -7,6 +7,9 @@ package org.hibernate.dialect.unique; import org.hibernate.boot.Metadata; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; +import org.hibernate.mapping.Column; +import org.hibernate.mapping.Table; import org.hibernate.mapping.UniqueKey; /** @@ -32,17 +35,34 @@ * @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. *

    * This is intended for dialects which do not support unique constraints * * @param column The column to which to apply the unique - * + * @param context A context for SQL string generation * @return The fragment (usually "unique"), empty string indicates the uniqueness will be indicated using a * different approach */ - public String getColumnDefinitionUniquenessFragment(org.hibernate.mapping.Column column); + 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 @@ -53,30 +73,82 @@ public interface UniqueDelegate { * 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 + * 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 + * @param context A context for SQL string generation * @return The fragment, typically in the form {@code ", unique(col1, col2), unique( col20)"}. NOTE: The leading * comma is important! */ - public String getTableCreationUniqueConstraintsFragment(org.hibernate.mapping.Table table); + 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. * + * @param uniqueKey The UniqueKey instance. Contains all information about the columns + * @param metadata Access to the bootstrap mapping information + * @param context A context for SQL string generation * @return The ALTER TABLE command */ - public String getAlterTableToAddUniqueKeyCommand(UniqueKey uniqueKey, Metadata metadata); + 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. * + * @param uniqueKey The UniqueKey instance. Contains all information about the columns + * @param metadata Access to the bootstrap mapping information + * @param context A context for SQL string generation * @return The ALTER TABLE command */ - public String getAlterTableToDropUniqueKeyCommand(UniqueKey uniqueKey, Metadata metadata); + 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/jdbc/env/spi/JdbcEnvironment.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/spi/JdbcEnvironment.java index 2a9fa57f0b6a..a0b48b68d699 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/spi/JdbcEnvironment.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/spi/JdbcEnvironment.java @@ -55,7 +55,9 @@ public interface JdbcEnvironment extends Service { * Obtain support for formatting qualified object names. * * @return Qualified name support. + * @deprecated Use a provided {@link org.hibernate.boot.model.relational.SqlStringGenerationContext} instead. */ + @Deprecated QualifiedObjectNameFormatter getQualifiedObjectNameFormatter(); /** diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jndi/JndiException.java b/hibernate-core/src/main/java/org/hibernate/engine/jndi/JndiException.java index 0d28b6632fb3..d18076e59698 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jndi/JndiException.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jndi/JndiException.java @@ -22,4 +22,14 @@ public class JndiException extends HibernateException { public JndiException(String message, Throwable cause) { super( message, cause ); } + + /** + * Constructs a JndiException + * + * @param message Message explaining the exception condition + */ + public JndiException(String message) { + super( message ); + } + } 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 48ae9156cc6f..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 @@ -6,6 +6,8 @@ */ package org.hibernate.engine.jndi.internal; +import java.net.URI; +import java.net.URISyntaxException; import java.util.Hashtable; import java.util.Map; import java.util.Properties; @@ -114,6 +116,16 @@ private InitialContext buildInitialContext() { } private Name parseName(String jndiName, Context context) { + try { + final URI uri = new URI( jndiName ); + final String scheme = uri.getScheme(); + if ( scheme != null && (! allowedScheme( scheme ) ) ) { + throw new JndiException( "JNDI lookups for scheme '" + scheme + "' are not allowed" ); + } + } + catch (URISyntaxException e) { + //Ok + } try { return context.getNameParser( "" ).parse( jndiName ); } @@ -125,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/SessionFactoryDelegatingImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionFactoryDelegatingImpl.java index 349e28f9fce8..e5269956c261 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionFactoryDelegatingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionFactoryDelegatingImpl.java @@ -30,6 +30,7 @@ import org.hibernate.StatelessSession; import org.hibernate.StatelessSessionBuilder; import org.hibernate.TypeHelper; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.boot.spi.SessionFactoryOptions; import org.hibernate.cache.spi.CacheImplementor; import org.hibernate.cfg.Settings; @@ -244,6 +245,11 @@ public JdbcServices getJdbcServices() { return delegate.getJdbcServices(); } + @Override + public SqlStringGenerationContext getSqlStringGenerationContext() { + return delegate.getSqlStringGenerationContext(); + } + @Override public Dialect getDialect() { return delegate.getDialect(); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionFactoryImplementor.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionFactoryImplementor.java index 0a78018a4f1d..ddbab41b53e1 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionFactoryImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionFactoryImplementor.java @@ -21,6 +21,7 @@ import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.SessionFactoryObserver; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.boot.spi.SessionFactoryOptions; import org.hibernate.cache.spi.CacheImplementor; import org.hibernate.cfg.Settings; @@ -298,6 +299,8 @@ default Dialect getDialect() { return getJdbcServices().getDialect(); } + SqlStringGenerationContext getSqlStringGenerationContext(); + /** * Retrieves the SQLExceptionConverter in effect for this SessionFactory. * 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 73b7d6afecba..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 @@ -897,7 +908,7 @@ protected void postProcessInsert(AST insert) throws SemanticException, QueryExce } final String fragment = capableGenerator.determineBulkInsertionIdentifierGenerationSelectFragment( - sessionFactoryHelper.getFactory().getDialect() + sessionFactoryHelper.getFactory().getSqlStringGenerationContext() ); if ( fragment != null ) { // we got a fragment from the generator, so alter the sql tree... @@ -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/AbstractMultiTableBulkIdStrategyImpl.java b/hibernate-core/src/main/java/org/hibernate/hql/spi/id/AbstractMultiTableBulkIdStrategyImpl.java index 71f4e763953b..347440b2a938 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/spi/id/AbstractMultiTableBulkIdStrategyImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/spi/id/AbstractMultiTableBulkIdStrategyImpl.java @@ -12,6 +12,7 @@ import org.hibernate.QueryException; import org.hibernate.boot.model.relational.QualifiedTableName; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.boot.spi.MetadataBuildingOptions; import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.boot.spi.SessionFactoryOptions; @@ -48,7 +49,8 @@ public final void prepare( JdbcServices jdbcServices, JdbcConnectionAccess connectionAccess, MetadataImplementor metadata, - SessionFactoryOptions sessionFactoryOptions) { + SessionFactoryOptions sessionFactoryOptions, + SqlStringGenerationContext sqlStringGenerationContext) { // build/get Table representation of the bulk-id tables - subclasses need hooks // for each: // handle DDL @@ -66,12 +68,8 @@ public final void prepare( continue; } - final String idTableName = jdbcEnvironment.getQualifiedObjectNameFormatter().format( - determineIdTableName( jdbcEnvironment, entityBinding ), - jdbcEnvironment.getDialect() - ); - final Table idTable = new Table(); - idTable.setName( idTableName ); + final QualifiedTableName idTableName = determineIdTableName( jdbcEnvironment, entityBinding ); + final Table idTable = new Table( idTableName.getCatalogName(), idTableName.getSchemaName(), idTableName.getTableName(), false ); idTable.setComment( "Used to hold id values for the " + entityBinding.getEntityName() + " entity" ); Iterator itr = entityBinding.getTable().getPrimaryKey().getColumnIterator(); @@ -81,7 +79,9 @@ public final void prepare( } augmentIdTableDefinition( idTable ); - final TT idTableInfo = buildIdTableInfo( entityBinding, idTable, jdbcServices, metadata, context ); + final TT idTableInfo = buildIdTableInfo( entityBinding, idTable, jdbcServices, metadata, context, + sqlStringGenerationContext + ); idTableInfoMap.put( entityBinding.getEntityName(), idTableInfo ); } @@ -125,16 +125,17 @@ protected abstract TT buildIdTableInfo( Table idTable, JdbcServices jdbcServices, MetadataImplementor metadata, - CT context); + CT context, + SqlStringGenerationContext sqlStringGenerationContext); - protected String buildIdTableCreateStatement(Table idTable, JdbcServices jdbcServices, MetadataImplementor metadata) { - final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment(); - final Dialect dialect = jdbcEnvironment.getDialect(); + protected String buildIdTableCreateStatement(Table idTable, MetadataImplementor metadata, + SqlStringGenerationContext sqlStringGenerationContext) { + final Dialect dialect = sqlStringGenerationContext.getDialect(); StringBuilder buffer = new StringBuilder( getIdTableSupport().getCreateIdTableCommand() ) .append( ' ' ) - .append( jdbcEnvironment.getQualifiedObjectNameFormatter().format( idTable.getQualifiedTableName(), dialect ) ) + .append( formatIdTableName( idTable.getQualifiedTableName(), sqlStringGenerationContext ) ) .append( " (" ); Iterator itr = idTable.getColumnIterator(); @@ -169,12 +170,19 @@ protected String buildIdTableCreateStatement(Table idTable, JdbcServices jdbcSer return buffer.toString(); } - protected String buildIdTableDropStatement(Table idTable, JdbcServices jdbcServices) { - final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment(); - final Dialect dialect = jdbcEnvironment.getDialect(); - + protected String buildIdTableDropStatement(Table idTable, SqlStringGenerationContext sqlStringGenerationContext) { return getIdTableSupport().getDropIdTableCommand() + " " - + jdbcEnvironment.getQualifiedObjectNameFormatter().format( idTable.getQualifiedTableName(), dialect ); + + formatIdTableName( idTable.getQualifiedTableName(), sqlStringGenerationContext ); + } + + protected String formatIdTableName(QualifiedTableName qualifiedTableName, + SqlStringGenerationContext sqlStringGenerationContext) { + // Historically, we've always ignored the default catalog/schema here, + // so for the sake of backwards compatibility, we will continue that way. + // Also, we must not use the default catalog/schema for temporary tables, + // because some vendors don't allow creating temporary tables + // in just any catalog/schema (for postgres, it must be a "temporary schema"). + return sqlStringGenerationContext.formatWithoutDefaults( qualifiedTableName ); } protected void finishPreparation( diff --git a/hibernate-core/src/main/java/org/hibernate/hql/spi/id/MultiTableBulkIdStrategy.java b/hibernate-core/src/main/java/org/hibernate/hql/spi/id/MultiTableBulkIdStrategy.java index 76854f0ecd78..b9c6c95148cb 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/spi/id/MultiTableBulkIdStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/spi/id/MultiTableBulkIdStrategy.java @@ -6,6 +6,7 @@ */ package org.hibernate.hql.spi.id; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.boot.spi.SessionFactoryOptions; import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess; @@ -22,21 +23,47 @@ * @author Steve Ebersole */ public interface MultiTableBulkIdStrategy { + + /** + * Prepare the strategy. Called as the SessionFactory is being built. Intended patterns here include:
      + *
    • Adding tables to the passed Mappings, to be picked by by "schema management tools"
    • + *
    • Manually creating the tables immediately through the passed JDBC Connection access
    • + *
    + * @param jdbcServices The JdbcService object + * @param connectionAccess Access to the JDBC Connection + * @param metadata Access to the O/RM mapping information + * @param sessionFactoryOptions + * @deprecated Will be removed in favor of the variant accepting a {@link SqlStringGenerationContext} + * @see #prepare(JdbcServices, JdbcConnectionAccess, MetadataImplementor, SessionFactoryOptions, SqlStringGenerationContext) + */ + @Deprecated + default void prepare( + JdbcServices jdbcServices, + JdbcConnectionAccess connectionAccess, + MetadataImplementor metadata, + SessionFactoryOptions sessionFactoryOptions) { + throw new IllegalStateException("prepare() was not implemented!"); + } + /** * Prepare the strategy. Called as the SessionFactory is being built. Intended patterns here include:
      *
    • Adding tables to the passed Mappings, to be picked by by "schema management tools"
    • *
    • Manually creating the tables immediately through the passed JDBC Connection access
    • *
    - * @param jdbcServices The JdbcService object + * @param jdbcServices The JdbcService object * @param connectionAccess Access to the JDBC Connection * @param metadata Access to the O/RM mapping information * @param sessionFactoryOptions + * @param sqlStringGenerationContext */ - void prepare( + default void prepare( JdbcServices jdbcServices, JdbcConnectionAccess connectionAccess, MetadataImplementor metadata, - SessionFactoryOptions sessionFactoryOptions); + SessionFactoryOptions sessionFactoryOptions, + SqlStringGenerationContext sqlStringGenerationContext) { + prepare( jdbcServices, connectionAccess, metadata, sessionFactoryOptions ); + } /** * Release the strategy. Called as the SessionFactory is being shut down. 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 2f4b75e7fee0..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,13 +72,12 @@ protected String determineIdTableName(Queryable persister) { "HT_" + StringHelper.unquote( persister.getTableName(), jdbcEnvironment.getDialect() ) ).render(); - return jdbcEnvironment.getQualifiedObjectNameFormatter().format( + return persister.getFactory().getSqlStringGenerationContext().formatWithoutDefaults( new QualifiedTableName( Identifier.toIdentifier( catalog ), Identifier.toIdentifier( schema ), Identifier.toIdentifier( qualifiedTableName ) - ), - jdbcEnvironment.getDialect() + ) ); } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/spi/id/cte/CteValuesListBulkIdStrategy.java b/hibernate-core/src/main/java/org/hibernate/hql/spi/id/cte/CteValuesListBulkIdStrategy.java index 7a83da863b45..15848ee673b1 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/spi/id/cte/CteValuesListBulkIdStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/spi/id/cte/CteValuesListBulkIdStrategy.java @@ -6,6 +6,7 @@ */ package org.hibernate.hql.spi.id.cte; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.boot.spi.SessionFactoryOptions; import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess; @@ -55,7 +56,8 @@ public void prepare( JdbcServices jdbcServices, JdbcConnectionAccess jdbcConnectionAccess, MetadataImplementor metadataImplementor, - SessionFactoryOptions sessionFactoryOptions) { + SessionFactoryOptions sessionFactoryOptions, + SqlStringGenerationContext sqlStringGenerationContext) { // nothing to do } 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 c1cbed9c660b..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 @@ -8,6 +8,7 @@ import java.sql.PreparedStatement; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.boot.registry.StandardServiceRegistry; import org.hibernate.boot.spi.MetadataBuildingOptions; import org.hibernate.boot.spi.MetadataImplementor; @@ -104,16 +105,14 @@ protected IdTableInfoImpl buildIdTableInfo( Table idTable, JdbcServices jdbcServices, MetadataImplementor metadata, - PreparationContextImpl context) { - context.creationStatements.add( buildIdTableCreateStatement( idTable, jdbcServices, metadata ) ); + PreparationContextImpl context, + SqlStringGenerationContext sqlStringGenerationContext) { + context.creationStatements.add( buildIdTableCreateStatement( idTable, metadata, sqlStringGenerationContext ) ); if ( dropIdTables ) { - context.dropStatements.add( buildIdTableDropStatement( idTable, jdbcServices ) ); + context.dropStatements.add( buildIdTableDropStatement( idTable, sqlStringGenerationContext ) ); } - final String renderedName = jdbcServices.getJdbcEnvironment().getQualifiedObjectNameFormatter().format( - idTable.getQualifiedTableName(), - jdbcServices.getJdbcEnvironment().getDialect() - ); + final String renderedName = sqlStringGenerationContext.formatWithoutDefaults( idTable.getQualifiedTableName() ); return new IdTableInfoImpl( renderedName ); } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/spi/id/inline/InlineIdsInClauseBulkIdStrategy.java b/hibernate-core/src/main/java/org/hibernate/hql/spi/id/inline/InlineIdsInClauseBulkIdStrategy.java index 90b95a1e8947..be03521fb2a0 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/spi/id/inline/InlineIdsInClauseBulkIdStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/spi/id/inline/InlineIdsInClauseBulkIdStrategy.java @@ -6,6 +6,7 @@ */ package org.hibernate.hql.spi.id.inline; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.boot.spi.SessionFactoryOptions; import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess; @@ -43,7 +44,8 @@ public void prepare( JdbcServices jdbcServices, JdbcConnectionAccess jdbcConnectionAccess, MetadataImplementor metadataImplementor, - SessionFactoryOptions sessionFactoryOptions) { + SessionFactoryOptions sessionFactoryOptions, + SqlStringGenerationContext sqlStringGenerationContext) { // nothing to do } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/spi/id/inline/InlineIdsOrClauseBulkIdStrategy.java b/hibernate-core/src/main/java/org/hibernate/hql/spi/id/inline/InlineIdsOrClauseBulkIdStrategy.java index 0ec0204f1fca..1c479e1d9d29 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/spi/id/inline/InlineIdsOrClauseBulkIdStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/spi/id/inline/InlineIdsOrClauseBulkIdStrategy.java @@ -6,6 +6,7 @@ */ package org.hibernate.hql.spi.id.inline; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.boot.spi.SessionFactoryOptions; import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess; @@ -45,7 +46,8 @@ public void prepare( JdbcServices jdbcServices, JdbcConnectionAccess jdbcConnectionAccess, MetadataImplementor metadataImplementor, - SessionFactoryOptions sessionFactoryOptions) { + SessionFactoryOptions sessionFactoryOptions, + SqlStringGenerationContext sqlStringGenerationContext) { // nothing to do } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/spi/id/inline/InlineIdsSubSelectValueListBulkIdStrategy.java b/hibernate-core/src/main/java/org/hibernate/hql/spi/id/inline/InlineIdsSubSelectValueListBulkIdStrategy.java index 09c15d520f11..7e6578357fb9 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/spi/id/inline/InlineIdsSubSelectValueListBulkIdStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/spi/id/inline/InlineIdsSubSelectValueListBulkIdStrategy.java @@ -6,6 +6,7 @@ */ package org.hibernate.hql.spi.id.inline; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.boot.spi.SessionFactoryOptions; import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess; @@ -48,7 +49,8 @@ public void prepare( JdbcServices jdbcServices, JdbcConnectionAccess jdbcConnectionAccess, MetadataImplementor metadataImplementor, - SessionFactoryOptions sessionFactoryOptions) { + SessionFactoryOptions sessionFactoryOptions, + SqlStringGenerationContext sqlStringGenerationContext) { // nothing to do } 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 68c2088932ba..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 @@ -7,6 +7,7 @@ package org.hibernate.hql.spi.id.local; import org.hibernate.boot.TempTableDdlTransactionHandling; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.boot.registry.StandardServiceRegistry; import org.hibernate.boot.spi.MetadataBuildingOptions; import org.hibernate.boot.spi.MetadataImplementor; @@ -111,17 +112,15 @@ protected IdTableInfoImpl buildIdTableInfo( Table idTable, JdbcServices jdbcServices, MetadataImplementor metadata, - PreparationContextImpl context) { - String dropStatement = buildIdTableDropStatement( idTable, jdbcServices ); + PreparationContextImpl context, + SqlStringGenerationContext sqlStringGenerationContext) { + String dropStatement = buildIdTableDropStatement( idTable, sqlStringGenerationContext ); if ( dropIdTables ) { context.dropStatements.add( dropStatement ); } return new IdTableInfoImpl( - jdbcServices.getJdbcEnvironment().getQualifiedObjectNameFormatter().format( - idTable.getQualifiedTableName(), - jdbcServices.getJdbcEnvironment().getDialect() - ), - buildIdTableCreateStatement( idTable, jdbcServices, metadata ), + 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 04c2cf0c04e0..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 @@ -10,6 +10,7 @@ import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.relational.QualifiedTableName; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.boot.registry.StandardServiceRegistry; import org.hibernate.boot.spi.MetadataBuildingOptions; import org.hibernate.boot.spi.MetadataImplementor; @@ -124,15 +125,15 @@ protected IdTableInfoImpl buildIdTableInfo( Table idTable, JdbcServices jdbcServices, MetadataImplementor metadata, - PreparationContextImpl context) { - final String renderedName = jdbcServices.getJdbcEnvironment().getQualifiedObjectNameFormatter().format( - idTable.getQualifiedTableName(), - jdbcServices.getJdbcEnvironment().getDialect() - ); + PreparationContextImpl context, + SqlStringGenerationContext sqlStringGenerationContext) { + final String renderedName = sqlStringGenerationContext.formatWithoutDefaults( idTable.getQualifiedTableName() ); - context.creationStatements.add( buildIdTableCreateStatement( idTable, jdbcServices, metadata ) ); + context.creationStatements.add( buildIdTableCreateStatement( idTable, metadata, + sqlStringGenerationContext + ) ); if ( dropIdTables ) { - context.dropStatements.add( buildIdTableDropStatement( idTable, jdbcServices ) ); + context.dropStatements.add( buildIdTableDropStatement( idTable, sqlStringGenerationContext ) ); } return new IdTableInfoImpl( renderedName ); diff --git a/hibernate-core/src/main/java/org/hibernate/id/AbstractPostInsertGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/AbstractPostInsertGenerator.java index ca57bc0dd3c9..1e0ba78e2af1 100755 --- a/hibernate-core/src/main/java/org/hibernate/id/AbstractPostInsertGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/AbstractPostInsertGenerator.java @@ -8,6 +8,7 @@ import java.io.Serializable; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.dialect.Dialect; import org.hibernate.engine.spi.SharedSessionContractImplementor; @@ -29,7 +30,7 @@ public boolean supportsBulkInsertionIdentifierGeneration() { } @Override - public String determineBulkInsertionIdentifierGenerationSelectFragment(Dialect dialect) { + public String determineBulkInsertionIdentifierGenerationSelectFragment(SqlStringGenerationContext context) { return null; } } diff --git a/hibernate-core/src/main/java/org/hibernate/id/Assigned.java b/hibernate-core/src/main/java/org/hibernate/id/Assigned.java index e22faa0f7df7..cfffdcc2107f 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/Assigned.java +++ b/hibernate-core/src/main/java/org/hibernate/id/Assigned.java @@ -23,7 +23,7 @@ * * @author Gavin King */ -public class Assigned implements IdentifierGenerator, Configurable { +public class Assigned implements IdentifierGenerator { private String entityName; public Serializable generate(SharedSessionContractImplementor session, Object obj) throws HibernateException { 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 c3baa8b84460..21c592d834d8 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/BulkInsertionCapableIdentifierGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/BulkInsertionCapableIdentifierGenerator.java @@ -6,6 +6,7 @@ */ package org.hibernate.id; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.dialect.Dialect; /** @@ -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(Dialect dialect); + 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 4b45876a8e73..5ee2a9eaa94a 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/CompositeNestedGeneratedValueGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/CompositeNestedGeneratedValueGenerator.java @@ -9,11 +9,15 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.List; +import java.util.Properties; import org.hibernate.HibernateException; import org.hibernate.boot.model.relational.Database; import org.hibernate.boot.model.relational.ExportableProducer; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.service.ServiceRegistry; +import org.hibernate.type.Type; /** * For composite identifiers, defines a number of "nested" generations that @@ -67,6 +71,17 @@ public interface GenerationContextLocator { * determined {@link GenerationContextLocator#locateGenerationContext context} */ public interface GenerationPlan extends ExportableProducer { + + /** + * Initializes this instance, in particular pre-generates SQL as necessary. + *

    + * This method is called after {@link #registerExportables(Database)}, before first use. + * + * @param context A context to help generate SQL strings + */ + default void initialize(SqlStringGenerationContext context) { + } + /** * Execute the value generation. * @@ -106,4 +121,11 @@ public void registerExportables(Database database) { plan.registerExportables( database ); } } + + @Override + public void initialize(SqlStringGenerationContext context) { + for (GenerationPlan plan : generationPlans) { + plan.initialize( context ); + } + } } diff --git a/hibernate-core/src/main/java/org/hibernate/id/Configurable.java b/hibernate-core/src/main/java/org/hibernate/id/Configurable.java index 7ffdcc970063..87d53fba0dd0 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/Configurable.java +++ b/hibernate-core/src/main/java/org/hibernate/id/Configurable.java @@ -15,11 +15,14 @@ /** * An {@link IdentifierGenerator} that supports "configuration". * + * @deprecated All methods are already defined in {@link IdentifierGenerator}. + * Just implement {@link IdentifierGenerator}. * @see IdentifierGenerator * * @author Gavin King * @author Steve Ebersole */ +@Deprecated public interface Configurable { /** * Configure this instance, given the value of parameters diff --git a/hibernate-core/src/main/java/org/hibernate/id/ForeignGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/ForeignGenerator.java index d7a00d37a9d1..6528d81c133a 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/ForeignGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/ForeignGenerator.java @@ -34,7 +34,7 @@ * * @author Gavin King */ -public class ForeignGenerator implements IdentifierGenerator, Configurable { +public class ForeignGenerator implements IdentifierGenerator { private static final CoreMessageLogger LOG = messageLogger( ForeignGenerator.class ); private String entityName; diff --git a/hibernate-core/src/main/java/org/hibernate/id/IdentifierGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/IdentifierGenerator.java index 51bc8a355e5b..41207aab4e7f 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/IdentifierGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/IdentifierGenerator.java @@ -7,10 +7,17 @@ package org.hibernate.id; import java.io.Serializable; +import java.util.Properties; import javax.persistence.GeneratedValue; import org.hibernate.HibernateException; +import org.hibernate.MappingException; +import org.hibernate.boot.model.relational.Database; +import org.hibernate.boot.model.relational.ExportableProducer; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.service.ServiceRegistry; +import org.hibernate.type.Type; /** * The general contract between a class that generates unique @@ -29,9 +36,8 @@ * @author Gavin King * * @see PersistentIdentifierGenerator - * @see Configurable */ -public interface IdentifierGenerator { +public interface IdentifierGenerator extends Configurable, ExportableProducer { /** * The configuration parameter holding the entity name */ @@ -48,6 +54,42 @@ public interface IdentifierGenerator { */ String GENERATOR_NAME = "GENERATOR_NAME"; + /** + * Configure this instance, given the value of parameters + * specified by the user as <param> elements. + *

    + * This method is called just once, following instantiation, and before {@link #registerExportables(Database)}. + * + * @param type The id property type descriptor + * @param params param values, keyed by parameter name + * @param serviceRegistry Access to service that may be needed. + * @throws MappingException If configuration fails. + */ + @Override + default void configure(Type type, Properties params, ServiceRegistry serviceRegistry) throws MappingException { + } + + /** + * Register database objects used by this identifier generator, e.g. sequences, tables, etc. + *

    + * This method is called just once, after {@link #configure(Type, Properties, ServiceRegistry)}. + * + * @param database The database instance + */ + @Override + default void registerExportables(Database database) { + } + + /** + * Initializes this instance, in particular pre-generates SQL as necessary. + *

    + * This method is called after {@link #registerExportables(Database)}, before first use. + * + * @param context A context to help generate SQL strings + */ + default void initialize(SqlStringGenerationContext context) { + } + /** * Generate a new identifier. * diff --git a/hibernate-core/src/main/java/org/hibernate/id/IdentityGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/IdentityGenerator.java index cb42f197b7d5..5b3e307c8135 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/IdentityGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/IdentityGenerator.java @@ -13,6 +13,7 @@ import org.hibernate.AssertionFailure; import org.hibernate.HibernateException; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.dialect.Dialect; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.id.insert.AbstractReturningDelegate; @@ -67,7 +68,7 @@ public InsertSelectDelegate(PostInsertIdentityPersister persister, Dialect diale } @Override - public IdentifierGeneratingInsert prepareIdentifierGeneratingInsert() { + public IdentifierGeneratingInsert prepareIdentifierGeneratingInsert(SqlStringGenerationContext context) { InsertSelectIdentityInsert insert = new InsertSelectIdentityInsert( dialect ); insert.addIdentityColumn( persister.getRootTableKeyColumnNames()[0] ); return insert; @@ -120,7 +121,7 @@ public BasicDelegate(PostInsertIdentityPersister persister, Dialect dialect) { } @Override - public IdentifierGeneratingInsert prepareIdentifierGeneratingInsert() { + public IdentifierGeneratingInsert prepareIdentifierGeneratingInsert(SqlStringGenerationContext context) { IdentifierGeneratingInsert insert = new IdentifierGeneratingInsert( dialect ); insert.addIdentityColumn( persister.getRootTableKeyColumnNames()[0] ); return insert; diff --git a/hibernate-core/src/main/java/org/hibernate/id/IncrementGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/IncrementGenerator.java index 68ab1517aae9..bfb9ccf51fbe 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/IncrementGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/IncrementGenerator.java @@ -10,11 +10,17 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; import java.util.Properties; import org.hibernate.HibernateException; import org.hibernate.MappingException; +import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.naming.ObjectNameNormalizer; +import org.hibernate.boot.model.relational.QualifiedTableName; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; +import org.hibernate.engine.jdbc.env.spi.IdentifierHelper; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.CoreLogging; @@ -38,14 +44,24 @@ * @author Steve Ebersole * @author Brett Meyer */ -public class IncrementGenerator implements IdentifierGenerator, Configurable { +public class IncrementGenerator implements IdentifierGenerator { private static final CoreMessageLogger LOG = CoreLogging.messageLogger( IncrementGenerator.class ); private Class returnClass; + private String column; + private List physicalTableNames; private String sql; private IntegralDataTypeHolder previousValueHolder; + /** + * @deprecated Exposed for tests only. + */ + @Deprecated + public String[] getAllSqlForTests() { + return new String[] { sql }; + } + @Override public synchronized Serializable generate(SharedSessionContractImplementor session, Object object) throws HibernateException { if ( sql != null ) { @@ -62,17 +78,13 @@ public void configure(Type type, Properties params, ServiceRegistry serviceRegis final ObjectNameNormalizer normalizer = (ObjectNameNormalizer) params.get( PersistentIdentifierGenerator.IDENTIFIER_NORMALIZER ); - String column = params.getProperty( "column" ); + column = params.getProperty( "column" ); if ( column == null ) { column = params.getProperty( PersistentIdentifierGenerator.PK ); } column = normalizer.normalizeIdentifierQuoting( column ).render( jdbcEnvironment.getDialect() ); - String tableList = params.getProperty( "tables" ); - if ( tableList == null ) { - tableList = params.getProperty( PersistentIdentifierGenerator.TABLES ); - } - String[] tables = StringHelper.split( ", ", tableList ); + IdentifierHelper identifierHelper = jdbcEnvironment.getIdentifierHelper(); final String schema = normalizer.toDatabaseIdentifierText( params.getProperty( PersistentIdentifierGenerator.SCHEMA ) @@ -81,23 +93,40 @@ public void configure(Type type, Properties params, ServiceRegistry serviceRegis params.getProperty( PersistentIdentifierGenerator.CATALOG ) ); + String tableList = params.getProperty( "tables" ); + if ( tableList == null ) { + tableList = params.getProperty( PersistentIdentifierGenerator.TABLES ); + } + physicalTableNames = new ArrayList<>(); + for ( String tableName : StringHelper.split( ", ", tableList ) ) { + physicalTableNames.add( new QualifiedTableName( identifierHelper.toIdentifier( catalog ), + identifierHelper.toIdentifier( schema ), identifierHelper.toIdentifier( tableName ) ) ); + } + } + + @Override + public void initialize(SqlStringGenerationContext context) { StringBuilder buf = new StringBuilder(); - for ( int i = 0; i < tables.length; i++ ) { - final String tableName = normalizer.toDatabaseIdentifierText( tables[i] ); - if ( tables.length > 1 ) { + for ( int i = 0; i < physicalTableNames.size(); i++ ) { + final String tableName = context.format( physicalTableNames.get( i ) ); + if ( physicalTableNames.size() > 1 ) { buf.append( "select max(" ).append( column ).append( ") as mx from " ); } - buf.append( Table.qualify( catalog, schema, tableName ) ); - if ( i < tables.length - 1 ) { + buf.append( tableName ); + if ( i < physicalTableNames.size() - 1 ) { buf.append( " union " ); } } - if ( tables.length > 1 ) { + String maxColumn; + if ( physicalTableNames.size() > 1 ) { buf.insert( 0, "( " ).append( " ) ids_" ); - column = "ids_.mx"; + maxColumn = "ids_.mx"; + } + else { + maxColumn = column; } - sql = "select max(" + column + ") from " + buf.toString(); + sql = "select max(" + maxColumn + ") from " + buf.toString(); } private void initializePreviousValueHolder(SharedSessionContractImplementor session) { 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 6c7c1000d808..bc077ec9415b 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/MultipleHiLoPerTableGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/MultipleHiLoPerTableGenerator.java @@ -14,7 +14,6 @@ import java.sql.Types; import java.util.Properties; -import org.hibernate.HibernateException; import org.hibernate.LockMode; import org.hibernate.MappingException; import org.hibernate.boot.model.naming.Identifier; @@ -22,6 +21,7 @@ import org.hibernate.boot.model.relational.Namespace; 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; @@ -78,7 +78,7 @@ * @deprecated Use {@link org.hibernate.id.enhanced.TableGenerator} instead. */ @Deprecated -public class MultipleHiLoPerTableGenerator implements PersistentIdentifierGenerator, Configurable { +public class MultipleHiLoPerTableGenerator implements PersistentIdentifierGenerator { private static final CoreMessageLogger LOG = CoreLogging.messageLogger( MultipleHiLoPerTableGenerator.class ); public static final String ID_TABLE = "table"; @@ -93,7 +93,9 @@ public class MultipleHiLoPerTableGenerator implements PersistentIdentifierGenera private static final String DEFAULT_VALUE_COLUMN = "sequence_next_hi_value"; private QualifiedName qualifiedTableName; - private String tableName; + private QualifiedName physicalTableName; + @Deprecated + private String formattedTableNameForLegacyGetter; private String segmentColumnName; private String segmentName; private String valueColumnName; @@ -177,7 +179,7 @@ public IntegralDataTypeHolder execute(Connection connection) throws SQLException rows = executeUpdate( updatePreparedStatement, statsCollector ); } catch (SQLException sqle) { - LOG.error( LOG.unableToUpdateHiValue( tableName ), sqle ); + LOG.error( LOG.unableToUpdateHiValue( physicalTableName.render() ), sqle ); throw sqle; } finally { @@ -253,6 +255,7 @@ private ResultSet executeQuery(PreparedStatement ps, SessionEventListenerManager } } + @Override @SuppressWarnings({"StatementWithEmptyBody", "deprecation"}) public void configure(Type type, Properties params, ServiceRegistry serviceRegistry) throws MappingException { returnClass = type.getReturnedClass(); @@ -341,23 +344,30 @@ public void registerExportables(Database database) { table.addColumn( valueColumn ); } - final JdbcEnvironment jdbcEnvironment = database.getJdbcEnvironment(); - // allow physical naming strategies a chance to kick in - tableName = jdbcEnvironment.getQualifiedObjectNameFormatter().format( - table.getQualifiedTableName(), - jdbcEnvironment.getDialect() + physicalTableName = table.getQualifiedTableName(); + + final JdbcEnvironment jdbcEnvironment = database.getJdbcEnvironment(); + final Dialect dialect = jdbcEnvironment.getDialect(); + this.formattedTableNameForLegacyGetter = jdbcEnvironment.getQualifiedObjectNameFormatter().format( + physicalTableName, + dialect ); + } + + @Override + public void initialize(SqlStringGenerationContext context) { + String formattedPhysicalTableName = context.format( physicalTableName ); query = "select " + valueColumnName + " from " + - jdbcEnvironment.getDialect().appendLockHint( LockMode.PESSIMISTIC_WRITE, tableName ) + + context.getDialect().appendLockHint( LockMode.PESSIMISTIC_WRITE, formattedPhysicalTableName ) + " where " + segmentColumnName + " = '" + segmentName + "'" + - jdbcEnvironment.getDialect().getForUpdateString(); + context.getDialect().getForUpdateString(); update = "update " + - tableName + + formattedPhysicalTableName + " set " + valueColumnName + " = ? where " + @@ -368,7 +378,7 @@ public void registerExportables(Database database) { segmentName + "'"; - insert = "insert into " + tableName + + insert = "insert into " + formattedPhysicalTableName + "(" + segmentColumnName + ", " + valueColumnName + ") " + "values('" + segmentName + "', ?)"; @@ -376,21 +386,8 @@ public void registerExportables(Database database) { } - public String[] sqlCreateStrings(Dialect dialect) throws HibernateException { - return new String[] { - dialect.getCreateTableString() - + ' ' + tableName + " ( " - + segmentColumnName + ' ' + dialect.getTypeName( Types.VARCHAR, keySize, 0, 0 ) + ", " - + valueColumnName + ' ' + dialect.getTypeName( Types.INTEGER ) - + " )" + dialect.getTableTypeString() - }; - } - - public String[] sqlDropStrings(Dialect dialect) throws HibernateException { - return new String[] {dialect.getDropTableString( tableName )}; - } - + @Deprecated public Object generatorKey() { - return tableName; + 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 6529124f98a3..5798d717a57a 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/PersistentIdentifierGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/PersistentIdentifierGenerator.java @@ -6,23 +6,25 @@ */ package org.hibernate.id; +import java.util.Properties; + import org.hibernate.HibernateException; -import org.hibernate.boot.model.relational.ExportableProducer; import org.hibernate.dialect.Dialect; +import org.hibernate.service.ServiceRegistry; +import org.hibernate.type.Type; /** * An IdentifierGenerator that requires creation of database objects. *

    - * All PersistentIdentifierGenerators that also implement - * Configurable have access to a special mapping parameter: schema + * All PersistentIdentifierGenerators have access to a special mapping parameter + * in their {@link #configure(Type, Properties, ServiceRegistry)} method: schema * * @author Gavin King * @author Steve Ebersole * * @see IdentifierGenerator - * @see Configurable */ -public interface PersistentIdentifierGenerator extends IdentifierGenerator, ExportableProducer { +public interface PersistentIdentifierGenerator extends IdentifierGenerator { /** * The configuration parameter holding the schema name @@ -57,37 +59,15 @@ public interface PersistentIdentifierGenerator extends IdentifierGenerator, Expo */ String IDENTIFIER_NORMALIZER = "identifier_normalizer"; - /** - * The SQL required to create the underlying database objects. - * - * @param dialect The dialect against which to generate the create command(s) - * - * @return The create command(s) - * - * @throws HibernateException problem creating the create command(s) - * @deprecated Utilize the ExportableProducer contract instead - */ - @Deprecated - String[] sqlCreateStrings(Dialect dialect) throws HibernateException; - - /** - * The SQL required to remove the underlying database objects. - * - * @param dialect The dialect against which to generate the drop command(s) - * - * @return The drop command(s) - * - * @throws HibernateException problem creating the drop command(s) - * @deprecated Utilize the ExportableProducer contract instead - */ - @Deprecated - String[] sqlDropStrings(Dialect dialect) throws HibernateException; - /** * 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. */ - Object generatorKey(); + @Deprecated + default Object generatorKey() { + return null; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/id/PostInsertIdentifierGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/PostInsertIdentifierGenerator.java index d23d86f2386f..8fc44924aafa 100755 --- a/hibernate-core/src/main/java/org/hibernate/id/PostInsertIdentifierGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/PostInsertIdentifierGenerator.java @@ -7,6 +7,7 @@ package org.hibernate.id; import org.hibernate.HibernateException; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.dialect.Dialect; import org.hibernate.id.insert.InsertGeneratedIdentifierDelegate; diff --git a/hibernate-core/src/main/java/org/hibernate/id/SelectGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/SelectGenerator.java index eb7521309e1b..76925c9d2efa 100755 --- a/hibernate-core/src/main/java/org/hibernate/id/SelectGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/SelectGenerator.java @@ -14,6 +14,7 @@ import org.hibernate.HibernateException; import org.hibernate.MappingException; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.dialect.Dialect; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.id.insert.AbstractSelectingDelegate; @@ -31,7 +32,7 @@ * * @author Gavin King */ -public class SelectGenerator extends AbstractPostInsertGenerator implements Configurable { +public class SelectGenerator extends AbstractPostInsertGenerator { private String uniqueKeyPropertyName; @Override @@ -39,6 +40,7 @@ public void configure(Type type, Properties params, ServiceRegistry serviceRegis uniqueKeyPropertyName = params.getProperty( "key" ); } + @Override public InsertGeneratedIdentifierDelegate getInsertGeneratedIdentifierDelegate( PostInsertIdentityPersister persister, Dialect dialect, @@ -102,7 +104,8 @@ private SelectGeneratorDelegate( idType = persister.getIdentifierType(); } - public IdentifierGeneratingInsert prepareIdentifierGeneratingInsert() { + @Override + public IdentifierGeneratingInsert prepareIdentifierGeneratingInsert(SqlStringGenerationContext context) { return new IdentifierGeneratingInsert( dialect ); } 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 cf82f58765e9..20742e057868 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/SequenceGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/SequenceGenerator.java @@ -12,7 +12,6 @@ import java.sql.SQLException; import java.util.Properties; -import org.hibernate.HibernateException; import org.hibernate.MappingException; import org.hibernate.boot.model.naming.ObjectNameNormalizer; import org.hibernate.boot.model.relational.Database; @@ -20,7 +19,7 @@ import org.hibernate.boot.model.relational.QualifiedName; import org.hibernate.boot.model.relational.QualifiedNameParser; import org.hibernate.boot.model.relational.Sequence; -import org.hibernate.dialect.Dialect; +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; @@ -45,7 +44,7 @@ */ @Deprecated public class SequenceGenerator - implements PersistentIdentifierGenerator, BulkInsertionCapableIdentifierGenerator, Configurable { + implements PersistentIdentifierGenerator, BulkInsertionCapableIdentifierGenerator { private static final Logger LOG = Logger.getLogger( SequenceGenerator.class.getName() ); @@ -65,7 +64,9 @@ public class SequenceGenerator public static final String PARAMETERS = "parameters"; private QualifiedName logicalQualifiedSequenceName; - private String sequenceName; + private QualifiedName physicalSequenceName; + @Deprecated + private String formattedSequenceNameForLegacyGetter; private Type identifierType; private String sql; @@ -73,12 +74,27 @@ protected Type getIdentifierType() { return identifierType; } + @Override + @Deprecated public Object generatorKey() { return getSequenceName(); } + @Deprecated public String getSequenceName() { - return sequenceName; + return formattedSequenceNameForLegacyGetter; + } + + public QualifiedName getPhysicalSequenceName() { + return physicalSequenceName; + } + + /** + * @deprecated Exposed for tests only. + */ + @Deprecated + public String[] getAllSqlForTests() { + return new String[] { sql }; } @Override @@ -144,25 +160,14 @@ protected IntegralDataTypeHolder buildHolder() { return IdentifierGeneratorHelper.getIntegralDataTypeHolder( identifierType.getReturnedClass() ); } - @Override - @SuppressWarnings( {"deprecation"}) - public String[] sqlCreateStrings(Dialect dialect) throws HibernateException { - return dialect.getCreateSequenceStrings( sequenceName, 1, 1 ); - } - - @Override - public String[] sqlDropStrings(Dialect dialect) throws HibernateException { - return dialect.getDropSequenceStrings( sequenceName ); - } - @Override public boolean supportsBulkInsertionIdentifierGeneration() { return true; } @Override - public String determineBulkInsertionIdentifierGenerationSelectFragment(Dialect dialect) { - return dialect.getSelectSequenceNextValString( getSequenceName() ); + public String determineBulkInsertionIdentifierGenerationSelectFragment(SqlStringGenerationContext context) { + return context.getDialect().getSelectSequenceNextValString( context.format( getPhysicalSequenceName() ) ); } @Override @@ -182,14 +187,16 @@ public void registerExportables(Database database) { 1 ); } + this.physicalSequenceName = sequence.getName(); final JdbcEnvironment jdbcEnvironment = database.getJdbcEnvironment(); - final Dialect dialect = jdbcEnvironment.getDialect(); + this.formattedSequenceNameForLegacyGetter = jdbcEnvironment.getQualifiedObjectNameFormatter() + .format( physicalSequenceName, jdbcEnvironment.getDialect() ); + } - this.sequenceName = jdbcEnvironment.getQualifiedObjectNameFormatter().format( - sequence.getName(), - dialect - ); - this.sql = jdbcEnvironment.getDialect().getSequenceNextValString( sequenceName ); + @Override + public void initialize(SqlStringGenerationContext context) { + String formattedPhysicalSequenceName = context.format( physicalSequenceName ); + this.sql = context.getDialect().getSequenceNextValString( formattedPhysicalSequenceName ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/id/SequenceIdentityGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/SequenceIdentityGenerator.java index b6ff0c2e3c7b..86d48a4a729e 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/SequenceIdentityGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/SequenceIdentityGenerator.java @@ -13,6 +13,8 @@ import org.hibernate.HibernateException; import org.hibernate.MappingException; +import org.hibernate.boot.model.relational.QualifiedName; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.dialect.Dialect; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.id.insert.AbstractReturningDelegate; @@ -55,7 +57,7 @@ public InsertGeneratedIdentifierDelegate getInsertGeneratedIdentifierDelegate( PostInsertIdentityPersister persister, Dialect dialect, boolean isGetGeneratedKeysEnabled) throws HibernateException { - return new Delegate( persister, dialect, getSequenceName() ); + return new Delegate( persister, getPhysicalSequenceName() ); } @Override @@ -64,22 +66,23 @@ public void configure(Type type, Properties params, ServiceRegistry serviceRegis } public static class Delegate extends AbstractReturningDelegate { - private final Dialect dialect; - private final String sequenceNextValFragment; + private final QualifiedName physicalSequenceName; private final String[] keyColumns; - public Delegate(PostInsertIdentityPersister persister, Dialect dialect, String sequenceName) { + public Delegate(PostInsertIdentityPersister persister, QualifiedName physicalSequenceName) { super( persister ); - this.dialect = dialect; - this.sequenceNextValFragment = dialect.getSelectSequenceNextValString( sequenceName ); + this.physicalSequenceName = physicalSequenceName; this.keyColumns = getPersister().getRootTableKeyColumnNames(); if ( keyColumns.length > 1 ) { throw new HibernateException( "sequence-identity generator cannot be used with with multi-column keys" ); } } - public IdentifierGeneratingInsert prepareIdentifierGeneratingInsert() { + @Override + public IdentifierGeneratingInsert prepareIdentifierGeneratingInsert(SqlStringGenerationContext context) { + Dialect dialect = context.getDialect(); NoCommentsInsert insert = new NoCommentsInsert( dialect ); + String sequenceNextValFragment = dialect.getSelectSequenceNextValString( context.format( physicalSequenceName ) ); insert.addColumn( getPersister().getRootTableKeyColumnNames()[0], sequenceNextValFragment ); return insert; } diff --git a/hibernate-core/src/main/java/org/hibernate/id/UUIDGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/UUIDGenerator.java index 0b86ee15cea0..a18597b1fd04 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/UUIDGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/UUIDGenerator.java @@ -39,7 +39,7 @@ * * @author Steve Ebersole */ -public class UUIDGenerator implements IdentifierGenerator, Configurable { +public class UUIDGenerator implements IdentifierGenerator { public static final String UUID_GEN_STRATEGY = "uuid_gen_strategy"; public static final String UUID_GEN_STRATEGY_CLASS = "uuid_gen_strategy_class"; diff --git a/hibernate-core/src/main/java/org/hibernate/id/UUIDHexGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/UUIDHexGenerator.java index a51c6fd9af18..a0370886de30 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/UUIDHexGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/UUIDHexGenerator.java @@ -29,7 +29,7 @@ * * @author Gavin King */ -public class UUIDHexGenerator extends AbstractUUIDGenerator implements Configurable { +public class UUIDHexGenerator extends AbstractUUIDGenerator { private static final CoreMessageLogger LOG = CoreLogging.messageLogger( UUIDHexGenerator.class ); private static boolean WARNED; 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 2e256864c94f..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 @@ -6,8 +6,10 @@ */ package org.hibernate.id.enhanced; +import org.hibernate.boot.model.relational.Database; import org.hibernate.boot.model.relational.ExportableProducer; -import org.hibernate.dialect.Dialect; +import org.hibernate.boot.model.relational.QualifiedName; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.engine.spi.SharedSessionContractImplementor; /** @@ -17,11 +19,26 @@ * @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). + *

    + * Only available after {@link #registerExportables(Database)} + * has been called. + * * @return The structure name. */ - String getName(); + QualifiedName getPhysicalName(); /** * How many times has this structure been accessed through this reference? @@ -55,22 +72,45 @@ public interface DatabaseStructure extends ExportableProducer { * but before first use. * * @param optimizer The optimizer being applied to the generator. + * + * @deprecated Use {@link #configure(Optimizer)} instead. + */ + @Deprecated + default void prepare(Optimizer optimizer) { + } + + /** + * Configures this structure with the given arguments. + *

    + * Called just after instantiation, before {@link #initialize(SqlStringGenerationContext)} + * + * @param optimizer The optimizer being applied to the generator. */ - void prepare(Optimizer optimizer); + default void configure(Optimizer optimizer) { + prepare( optimizer ); + } /** - * Commands needed to create the underlying structures. - * @param dialect The database dialect being used. - * @return The creation commands. + * Register database objects involved in this structure, e.g. sequences, tables, etc. + *

    + * This method is called just once, after {@link #configure(Optimizer)}, + * but before {@link #initialize(SqlStringGenerationContext)}. + * + * @param database The database instance */ - String[] sqlCreateStrings(Dialect dialect); + @Override + void registerExportables(Database database); /** - * Commands needed to drop the underlying structures. - * @param dialect The database dialect being used. - * @return The drop commands. + * Initializes this structure, in particular pre-generates SQL as necessary. + *

    + * This method is called just once, after {@link #registerExportables(Database)}, + * before first use. + * + * @param context A context to help generate SQL strings */ - String[] sqlDropStrings(Dialect dialect); + default void initialize(SqlStringGenerationContext context) { + } /** * Is the structure physically a sequence? @@ -78,4 +118,12 @@ public interface DatabaseStructure extends ExportableProducer { * @return {@code true} if the actual database structure is a sequence; {@code false} otherwise. */ boolean isPhysicalSequence(); + + /** + * @deprecated Exposed for tests only. + */ + @Deprecated + public default String[] getAllSqlForTests() { + return new String[] { }; + } } 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 b32ce3d9682d..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 @@ -11,12 +11,11 @@ import java.sql.SQLException; import org.hibernate.AssertionFailure; -import org.hibernate.HibernateException; import org.hibernate.boot.model.relational.Database; import org.hibernate.boot.model.relational.Namespace; import org.hibernate.boot.model.relational.QualifiedName; import org.hibernate.boot.model.relational.Sequence; -import org.hibernate.dialect.Dialect; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.id.IdentifierGeneratorHelper; @@ -44,7 +43,9 @@ public class SequenceStructure implements DatabaseStructure { private String sql; private boolean applyIncrementSizeToSourceValues; private int accessCounter; - protected String sequenceName; + @Deprecated + private String formattedSequenceNameForLegacyGetter; + protected QualifiedName physicalSequenceName; public SequenceStructure( JdbcEnvironment jdbcEnvironment, @@ -60,8 +61,14 @@ public SequenceStructure( } @Override + @Deprecated public String getName() { - return sequenceName; + return formattedSequenceNameForLegacyGetter; + } + + @Override + public QualifiedName getPhysicalName() { + return physicalSequenceName; } @Override @@ -79,6 +86,11 @@ public int getInitialValue() { return initialValue; } + @Override + public String[] getAllSqlForTests() { + return new String[] { sql }; + } + @Override public AccessCallback buildCallback(final SharedSessionContractImplementor session) { if ( sql == null ) { @@ -134,24 +146,18 @@ public String getTenantIdentifier() { } @Override - public void prepare(Optimizer optimizer) { + public void configure(Optimizer optimizer) { applyIncrementSizeToSourceValues = optimizer.applyIncrementSizeToSourceValues(); } @Override public void registerExportables(Database database) { buildSequence( database ); - this.sql = database.getJdbcEnvironment().getDialect().getSequenceNextValString( sequenceName ); } @Override - public String[] sqlCreateStrings(Dialect dialect) throws HibernateException { - return dialect.getCreateSequenceStrings( sequenceName, initialValue, getSourceIncrementSize() ); - } - - @Override - public String[] sqlDropStrings(Dialect dialect) throws HibernateException { - return dialect.getDropSequenceStrings( sequenceName ); + public void initialize(SqlStringGenerationContext context) { + this.sql = context.getDialect().getSequenceNextValString( context.format( physicalSequenceName ) ); } @Override @@ -182,9 +188,10 @@ protected void buildSequence(Database database) { sequence = namespace.createSequence( logicalQualifiedSequenceName.getObjectName(), initialValue, sourceIncrementSize ); } - this.sequenceName = database.getJdbcEnvironment().getQualifiedObjectNameFormatter().format( - sequence.getName(), - database.getJdbcEnvironment().getDialect() - ); + 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 d3d7de7e5c1e..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 @@ -12,11 +12,11 @@ import org.hibernate.HibernateException; import org.hibernate.MappingException; -import org.hibernate.boot.SchemaAutoTooling; import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.relational.Database; import org.hibernate.boot.model.relational.QualifiedName; import org.hibernate.boot.model.relational.QualifiedNameParser; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.cfg.AvailableSettings; import org.hibernate.dialect.Dialect; import org.hibernate.engine.config.spi.ConfigurationService; @@ -24,7 +24,6 @@ import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.id.BulkInsertionCapableIdentifierGenerator; -import org.hibernate.id.Configurable; import org.hibernate.id.IdentifierGenerator; import org.hibernate.id.PersistentIdentifierGenerator; import org.hibernate.id.SequenceMismatchStrategy; @@ -101,7 +100,7 @@ * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) */ public class SequenceStyleGenerator - implements PersistentIdentifierGenerator, BulkInsertionCapableIdentifierGenerator, Configurable { + implements PersistentIdentifierGenerator, BulkInsertionCapableIdentifierGenerator { private static final CoreMessageLogger LOG = Logger.getMessageLogger( CoreMessageLogger.class, @@ -299,7 +298,17 @@ public void configure(Type type, Properties params, ServiceRegistry serviceRegis incrementSize, ConfigurationHelper.getInt( INITIAL_PARAM, params, -1 ) ); - this.databaseStructure.prepare( optimizer ); + this.databaseStructure.configure( optimizer ); + } + + @Override + public void registerExportables(Database database) { + databaseStructure.registerExportables( database ); + } + + @Override + public void initialize(SqlStringGenerationContext context) { + this.databaseStructure.initialize( context ); } /** @@ -529,20 +538,11 @@ public Serializable generate(SharedSessionContractImplementor session, Object ob // PersistentIdentifierGenerator implementation ~~~~~~~~~~~~~~~~~~~~~~~~~~~ @Override + @Deprecated public Object generatorKey() { return databaseStructure.getName(); } - @Override - public String[] sqlCreateStrings(Dialect dialect) throws HibernateException { - return databaseStructure.sqlCreateStrings( dialect ); - } - - @Override - public String[] sqlDropStrings(Dialect dialect) throws HibernateException { - return databaseStructure.sqlDropStrings( dialect ); - } - // BulkInsertionCapableIdentifierGenerator implementation ~~~~~~~~~~~~~~~~~ @@ -556,13 +556,8 @@ public boolean supportsBulkInsertionIdentifierGeneration() { } @Override - public String determineBulkInsertionIdentifierGenerationSelectFragment(Dialect dialect) { - return dialect.getSelectSequenceNextValString( getDatabaseStructure().getName() ); - } - - @Override - public void registerExportables(Database database) { - databaseStructure.registerExportables( database ); + public String determineBulkInsertionIdentifierGenerationSelectFragment(SqlStringGenerationContext context) { + return context.getDialect().getSelectSequenceNextValString( context.format( getDatabaseStructure().getPhysicalName() ) ); } /** 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 5a961091953e..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 @@ -16,7 +16,6 @@ import java.util.Map; import java.util.Properties; -import org.hibernate.HibernateException; import org.hibernate.LockMode; import org.hibernate.LockOptions; import org.hibernate.MappingException; @@ -26,6 +25,7 @@ import org.hibernate.boot.model.relational.Namespace; import org.hibernate.boot.model.relational.QualifiedName; import org.hibernate.boot.model.relational.QualifiedNameParser; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.cfg.AvailableSettings; import org.hibernate.dialect.Dialect; import org.hibernate.engine.config.spi.ConfigurationService; @@ -36,7 +36,6 @@ import org.hibernate.engine.jdbc.spi.SqlStatementLogger; import org.hibernate.engine.spi.SessionEventListenerManager; import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.id.Configurable; import org.hibernate.id.ExportableColumn; import org.hibernate.id.IdentifierGenerator; import org.hibernate.id.IdentifierGeneratorHelper; @@ -129,7 +128,7 @@ * * @author Steve Ebersole */ -public class TableGenerator implements PersistentIdentifierGenerator, Configurable { +public class TableGenerator implements PersistentIdentifierGenerator { private static final CoreMessageLogger LOG = Logger.getMessageLogger( CoreMessageLogger.class, TableGenerator.class.getName() @@ -233,7 +232,7 @@ public class TableGenerator implements PersistentIdentifierGenerator, Configurab private Type identifierType; private QualifiedName qualifiedTableName; - private String renderedTableName; + private QualifiedName physicalTableName; private String segmentColumnName; private String segmentValue; @@ -251,6 +250,7 @@ public class TableGenerator implements PersistentIdentifierGenerator, Configurab private long accessCount; @Override + @Deprecated public Object generatorKey() { return qualifiedTableName.render(); } @@ -356,6 +356,14 @@ public final long getTableAccessCount() { return accessCount; } + /** + * @deprecated Exposed for tests only. + */ + @Deprecated + public String[] getAllSqlForTests() { + return new String[] { selectQuery, insertQuery, updateQuery }; + } + @Override public void configure(Type type, Properties params, ServiceRegistry serviceRegistry) throws MappingException { storeLastUsedValue = serviceRegistry.getService( ConfigurationService.class ) @@ -525,30 +533,31 @@ protected int determineIncrementSize(Properties params) { } @SuppressWarnings({"unchecked", "WeakerAccess"}) - protected String buildSelectQuery(Dialect dialect) { + protected String buildSelectQuery(String formattedPhysicalTableName, SqlStringGenerationContext context) { final String alias = "tbl"; final String query = "select " + StringHelper.qualify( alias, valueColumnName ) + - " from " + renderedTableName + ' ' + alias + + " from " + formattedPhysicalTableName + ' ' + alias + " where " + StringHelper.qualify( alias, segmentColumnName ) + "=?"; final LockOptions lockOptions = new LockOptions( LockMode.PESSIMISTIC_WRITE ); lockOptions.setAliasSpecificLockMode( alias, LockMode.PESSIMISTIC_WRITE ); final Map updateTargetColumnsMap = Collections.singletonMap( alias, new String[] { valueColumnName } ); - return dialect.applyLocksToSql( query, lockOptions, updateTargetColumnsMap ); + return context.getDialect().applyLocksToSql( query, lockOptions, updateTargetColumnsMap ); } @SuppressWarnings("WeakerAccess") - protected String buildUpdateQuery() { - return "update " + renderedTableName + + protected String buildUpdateQuery(String formattedPhysicalTableName, SqlStringGenerationContext context) { + return "update " + formattedPhysicalTableName + " set " + valueColumnName + "=? " + " where " + valueColumnName + "=? and " + segmentColumnName + "=?"; } @SuppressWarnings("WeakerAccess") - protected String buildInsertQuery() { - return "insert into " + renderedTableName + " (" + segmentColumnName + ", " + valueColumnName + ") " + " values (?,?)"; + protected String buildInsertQuery(String formattedPhysicalTableName, SqlStringGenerationContext context) { + return "insert into " + formattedPhysicalTableName + " (" + segmentColumnName + ", " + valueColumnName + ") " + " values (?,?)"; } - protected InitCommand generateInsertInitCommand() { + protected InitCommand generateInsertInitCommand(SqlStringGenerationContext context) { + String renderedTableName = context.format( physicalTableName ); int value = initialValue; if ( storeLastUsedValue ) { value = initialValue - 1; @@ -646,7 +655,7 @@ public IntegralDataTypeHolder execute(Connection connection) throws SQLException rows = executeUpdate( updatePS, statsCollector ); } catch (SQLException e) { - LOG.unableToUpdateQueryHiValue( renderedTableName, e ); + LOG.unableToUpdateQueryHiValue( physicalTableName.render(), e ); throw e; } } @@ -709,21 +718,6 @@ private ResultSet executeQuery(PreparedStatement ps, SessionEventListenerManager } } - @Override - public String[] sqlCreateStrings(Dialect dialect) throws HibernateException { - return new String[] { - dialect.getCreateTableString() + ' ' + renderedTableName + " ( " - + segmentColumnName + ' ' + dialect.getTypeName( Types.VARCHAR, segmentValueLength, 0, 0 ) + " not null " - + ", " + valueColumnName + ' ' + dialect.getTypeName( Types.BIGINT ) - + ", primary key ( " + segmentColumnName + " ) )" + dialect.getTableTypeString() - }; - } - - @Override - public String[] sqlDropStrings(Dialect dialect) throws HibernateException { - return new String[] { dialect.getDropTableString( renderedTableName ) }; - } - @Override public void registerExportables(Database database) { final Dialect dialect = database.getJdbcEnvironment().getDialect(); @@ -762,14 +756,15 @@ public void registerExportables(Database database) { } // allow physical naming strategies a chance to kick in - this.renderedTableName = database.getJdbcEnvironment().getQualifiedObjectNameFormatter().format( - table.getQualifiedTableName(), - dialect - ); - table.addInitCommand( generateInsertInitCommand() ); + this.physicalTableName = table.getQualifiedTableName(); + table.addInitCommand( this::generateInsertInitCommand ); + } - this.selectQuery = buildSelectQuery( dialect ); - this.updateQuery = buildUpdateQuery(); - this.insertQuery = buildInsertQuery(); + @Override + public void initialize(SqlStringGenerationContext context) { + String formattedPhysicalTableName = context.format( physicalTableName ); + this.selectQuery = buildSelectQuery( formattedPhysicalTableName, context ); + this.updateQuery = buildUpdateQuery( formattedPhysicalTableName, context ); + this.insertQuery = buildInsertQuery( formattedPhysicalTableName, context ); } } 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 5a2d5420df90..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 @@ -10,16 +10,15 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; -import java.sql.Types; import org.hibernate.AssertionFailure; -import org.hibernate.HibernateException; import org.hibernate.LockMode; import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.relational.Database; import org.hibernate.boot.model.relational.InitCommand; import org.hibernate.boot.model.relational.Namespace; import org.hibernate.boot.model.relational.QualifiedName; +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; @@ -55,7 +54,9 @@ public class TableStructure implements DatabaseStructure { private final int incrementSize; private final Class numberType; - private String tableNameText; + private QualifiedName physicalTableName; + @Deprecated + private String formattedTableNameForLegacyGetter; private String valueColumnNameText; private String selectQuery; @@ -80,8 +81,14 @@ public TableStructure( } @Override + @Deprecated public String getName() { - return tableNameText; + return formattedTableNameForLegacyGetter; + } + + @Override + public QualifiedName getPhysicalName() { + return physicalTableName; } @Override @@ -99,6 +106,11 @@ public int getTimesAccessed() { return accessCounter; } + @Override + public String[] getAllSqlForTests() { + return new String[] { selectQuery, updateQuery }; + } + @Override public void prepare(Optimizer optimizer) { applyIncrementSizeToSourceValues = optimizer.applyIncrementSizeToSourceValues(); @@ -137,7 +149,7 @@ public IntegralDataTypeHolder execute(Connection connection) throws SQLException )) { final ResultSet selectRS = executeQuery( selectStatement, statsCollector ); if ( !selectRS.next() ) { - final String err = "could not read a hi value - you need to populate the table: " + tableNameText; + final String err = "could not read a hi value - you need to populate the table: " + physicalTableName; LOG.error( err ); throw new IdentifierGenerationException( err ); } @@ -163,7 +175,7 @@ public IntegralDataTypeHolder execute(Connection connection) throws SQLException rows = executeUpdate( updatePS, statsCollector ); } catch (SQLException e) { - LOG.unableToUpdateQueryHiValue( tableNameText, e ); + LOG.unableToUpdateQueryHiValue( physicalTableName.render(), e ); throw e; } } while ( rows == 0 ); @@ -220,19 +232,6 @@ private ResultSet executeQuery(PreparedStatement ps, SessionEventListenerManager } } - @Override - public String[] sqlCreateStrings(Dialect dialect) throws HibernateException { - return new String[] { - dialect.getCreateTableString() + " " + tableNameText + " ( " + valueColumnNameText + " " + dialect.getTypeName( Types.BIGINT ) + " )", - "insert into " + tableNameText + " values ( " + initialValue + " )" - }; - } - - @Override - public String[] sqlDropStrings(Dialect dialect) throws HibernateException { - return new String[] { dialect.getDropTableString( tableNameText ) }; - } - @Override public boolean isPhysicalSequence() { return false; @@ -254,22 +253,12 @@ public void registerExportables(Database database) { table = namespace.createTable( logicalQualifiedTableName.getObjectName(), false ); tableCreated = true; } + this.physicalTableName = table.getQualifiedTableName(); - this.tableNameText = jdbcEnvironment.getQualifiedObjectNameFormatter().format( - table.getQualifiedTableName(), - dialect - ); - - this.valueColumnNameText = logicalValueColumnNameIdentifier.render( dialect ); - + this.formattedTableNameForLegacyGetter = jdbcEnvironment.getQualifiedObjectNameFormatter() + .format( physicalTableName, dialect ); - this.selectQuery = "select " + valueColumnNameText + " as id_val" + - " from " + dialect.appendLockHint( LockMode.PESSIMISTIC_WRITE, tableNameText ) + - dialect.getForUpdateString(); - - this.updateQuery = "update " + tableNameText + - " set " + valueColumnNameText + "= ?" + - " where " + valueColumnNameText + "=?"; + valueColumnNameText = logicalValueColumnNameIdentifier.render( dialect ); if ( tableCreated ) { ExportableColumn valueColumn = new ExportableColumn( database, @@ -280,9 +269,23 @@ public void registerExportables(Database database) { table.addColumn( valueColumn ); - table.addInitCommand( - new InitCommand( "insert into " + tableNameText + " values ( " + initialValue + " )" ) - ); + table.addInitCommand( context -> new InitCommand( "insert into " + + context.format( physicalTableName ) + " values ( " + initialValue + " )" ) ); } } + + @Override + public void initialize(SqlStringGenerationContext context) { + Dialect dialect = context.getDialect(); + + String formattedPhysicalTableName = context.format( physicalTableName ); + + this.selectQuery = "select " + valueColumnNameText + " as id_val" + + " from " + dialect.appendLockHint( LockMode.PESSIMISTIC_WRITE, formattedPhysicalTableName ) + + dialect.getForUpdateString(); + + this.updateQuery = "update " + formattedPhysicalTableName + + " set " + valueColumnNameText + "= ?" + + " where " + valueColumnNameText + "=?"; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/id/factory/internal/DefaultIdentifierGeneratorFactory.java b/hibernate-core/src/main/java/org/hibernate/id/factory/internal/DefaultIdentifierGeneratorFactory.java index 5d433606e632..b08e65021142 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/factory/internal/DefaultIdentifierGeneratorFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/id/factory/internal/DefaultIdentifierGeneratorFactory.java @@ -19,7 +19,6 @@ import org.hibernate.engine.config.spi.StandardConverters; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.id.Assigned; -import org.hibernate.id.Configurable; import org.hibernate.id.ForeignGenerator; import org.hibernate.id.GUIDGenerator; import org.hibernate.id.IdentifierGenerator; @@ -140,9 +139,7 @@ public boolean useJpaCompliantCreation() { FallbackBeanInstanceProducer.INSTANCE ).getBeanInstance(); } - if ( identifierGenerator instanceof Configurable ) { - ( ( Configurable ) identifierGenerator ).configure( type, config, serviceRegistry ); - } + identifierGenerator.configure( type, config, serviceRegistry ); return identifierGenerator; } catch ( Exception e ) { 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 98da2c2b370b..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 @@ -8,6 +8,7 @@ import java.io.Serializable; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.engine.spi.SharedSessionContractImplementor; /** @@ -26,8 +27,23 @@ public interface InsertGeneratedIdentifierDelegate { * of handling generated key values. * * @return The insert object. + * @deprecated Implement {@link #prepareIdentifierGeneratingInsert(SqlStringGenerationContext)} instead. */ - IdentifierGeneratingInsert prepareIdentifierGeneratingInsert(); + @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. + * + * @param context A context to help generate SQL strings + * @return The insert object. + */ + 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/FilterConfiguration.java b/hibernate-core/src/main/java/org/hibernate/internal/FilterConfiguration.java index 84461d642af3..33f169649b8e 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/FilterConfiguration.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/FilterConfiguration.java @@ -59,9 +59,7 @@ public Map getAliasTableMap(SessionFactoryImplementor factory) { } else if ( persistentClass != null ) { String table = persistentClass.getTable().getQualifiedName( - factory.getDialect(), - factory.getSettings().getDefaultCatalogName(), - factory.getSettings().getDefaultSchemaName() + factory.getSqlStringGenerationContext() ); return Collections.singletonMap( null, table ); } 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 87d51e7c514c..884c8476d300 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java @@ -18,7 +18,6 @@ import java.util.Map; import java.util.Set; import java.util.TimeZone; -import java.util.function.Function; import java.util.function.Supplier; import javax.naming.Reference; import javax.naming.StringRefAddr; @@ -49,6 +48,8 @@ import org.hibernate.TypeHelper; import org.hibernate.boot.cfgxml.spi.CfgXmlAccessService; import org.hibernate.boot.cfgxml.spi.LoadedConfig; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; +import org.hibernate.boot.model.relational.internal.SqlStringGenerationContextImpl; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.boot.spi.SessionFactoryOptions; @@ -171,6 +172,7 @@ public class SessionFactoryImpl implements SessionFactoryImplementor { private final transient SessionFactoryServiceRegistry serviceRegistry; private final transient EventEngine eventEngine; private final transient JdbcServices jdbcServices; + private final transient SqlStringGenerationContext sqlStringGenerationContext; private final transient SQLFunctionRegistry sqlFunctionRegistry; @@ -232,8 +234,10 @@ public SessionFactoryImpl( jdbcServices = serviceRegistry.getService( JdbcServices.class ); + ConfigurationService configurationService = serviceRegistry.getService( ConfigurationService.class ); + this.properties = new HashMap<>(); - this.properties.putAll( serviceRegistry.getService( ConfigurationService.class ).getSettings() ); + this.properties.putAll( configurationService.getSettings() ); if ( !properties.containsKey( AvailableSettings.JPA_VALIDATION_FACTORY ) && !properties.containsKey( AvailableSettings.JAKARTA_JPA_VALIDATION_FACTORY ) ) { if ( getSessionFactoryOptions().getValidatorFactoryReference() != null ) { @@ -251,6 +255,10 @@ public SessionFactoryImpl( maskOutSensitiveInformation(this.properties); logIfEmptyCompositesEnabled( this.properties ); + sqlStringGenerationContext = SqlStringGenerationContextImpl.fromExplicit( + jdbcServices.getJdbcEnvironment(), metadata.getDatabase(), + options.getDefaultCatalog(), options.getDefaultSchema() ); + this.sqlFunctionRegistry = new SQLFunctionRegistry( jdbcServices.getJdbcEnvironment().getDialect(), options.getCustomSqlFunctionMap() ); this.cacheAccess = this.serviceRegistry.getService( CacheImplementor.class ); this.criteriaBuilder = new CriteriaBuilderImpl( this ); @@ -298,10 +306,9 @@ public void sessionFactoryClosed(SessionFactory factory) { IdentifierGenerator generator = model.getIdentifier().createIdentifierGenerator( metadata.getIdentifierGeneratorFactory(), jdbcServices.getJdbcEnvironment().getDialect(), - settings.getDefaultCatalogName(), - settings.getDefaultSchemaName(), (RootClass) model ); + generator.initialize( sqlStringGenerationContext ); identifierGenerators.put( model.getEntityName(), generator ); } ); metadata.validate(); @@ -321,7 +328,8 @@ public void sessionFactoryClosed(SessionFactory factory) { jdbcServices, buildLocalConnectionAccess(), metadata, - sessionFactoryOptions + sessionFactoryOptions, + sqlStringGenerationContext ); SchemaManagementToolCoordinator.process( @@ -333,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() ) { @@ -391,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( @@ -560,6 +568,11 @@ public JdbcServices getJdbcServices() { return jdbcServices; } + @Override + public SqlStringGenerationContext getSqlStringGenerationContext() { + return sqlStringGenerationContext; + } + public IdentifierGeneratorFactory getIdentifierGeneratorFactory() { return null; } 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/custom/sql/SQLQueryParser.java b/hibernate-core/src/main/java/org/hibernate/loader/custom/sql/SQLQueryParser.java index 73efc374e559..16add7c282b3 100755 --- a/hibernate-core/src/main/java/org/hibernate/loader/custom/sql/SQLQueryParser.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/custom/sql/SQLQueryParser.java @@ -13,6 +13,8 @@ import java.util.regex.Pattern; import org.hibernate.QueryException; +import org.hibernate.boot.model.naming.Identifier; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.engine.query.spi.ParameterParser; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.param.ParameterBinder; @@ -85,6 +87,8 @@ protected String substituteBrackets(String sqlQuery) throws QueryException { StringBuilder result = new StringBuilder( sqlQuery.length() + 20 ); int left, right; + SqlStringGenerationContext sqlStringGenerationContext = factory.getSqlStringGenerationContext(); + // replace {....} with corresponding column aliases for ( int curr = 0; curr < sqlQuery.length(); curr = right + 1 ) { if ( ( left = sqlQuery.indexOf( '{', curr ) ) < 0 ) { @@ -107,30 +111,30 @@ protected String substituteBrackets(String sqlQuery) throws QueryException { if ( isPlaceholder ) { // Domain replacement if ( DOMAIN_PLACEHOLDER.equals( aliasPath ) ) { - final String catalogName = factory.getSettings().getDefaultCatalogName(); + final Identifier catalogName = sqlStringGenerationContext.getDefaultCatalog(); if ( catalogName != null ) { - result.append( catalogName ); + result.append( catalogName.render( sqlStringGenerationContext.getDialect() ) ); result.append( "." ); } - final String schemaName = factory.getSettings().getDefaultSchemaName(); + final Identifier schemaName = sqlStringGenerationContext.getDefaultSchema(); if ( schemaName != null ) { - result.append( schemaName ); + result.append( schemaName.render( sqlStringGenerationContext.getDialect() ) ); result.append( "." ); } } // Schema replacement else if ( SCHEMA_PLACEHOLDER.equals( aliasPath ) ) { - final String schemaName = factory.getSettings().getDefaultSchemaName(); + final Identifier schemaName = sqlStringGenerationContext.getDefaultSchema(); if ( schemaName != null ) { - result.append(schemaName); + result.append( schemaName.render( sqlStringGenerationContext.getDialect() ) ); result.append("."); } } // Catalog replacement else if ( CATALOG_PLACEHOLDER.equals( aliasPath ) ) { - final String catalogName = factory.getSettings().getDefaultCatalogName(); + final Identifier catalogName = sqlStringGenerationContext.getDefaultCatalog(); if ( catalogName != null ) { - result.append( catalogName ); + result.append( catalogName.render( sqlStringGenerationContext.getDialect() ) ); result.append( "." ); } } 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/AuxiliaryDatabaseObject.java b/hibernate-core/src/main/java/org/hibernate/mapping/AuxiliaryDatabaseObject.java index 22ef534bbdcb..f902bd8bac76 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/AuxiliaryDatabaseObject.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/AuxiliaryDatabaseObject.java @@ -15,7 +15,10 @@ * creating/dropping the schema. * * @author Steve Ebersole + * + * @deprecated Use {@link org.hibernate.boot.model.relational.AuxiliaryDatabaseObject} instead. */ +@Deprecated public interface AuxiliaryDatabaseObject extends RelationalModel, Serializable { /** * Add the given dialect name to the scope of dialects to which diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Component.java b/hibernate-core/src/main/java/org/hibernate/mapping/Component.java index af85ad6fbbc5..70e8d878afb7 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Component.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Component.java @@ -16,7 +16,7 @@ import org.hibernate.EntityMode; import org.hibernate.MappingException; import org.hibernate.boot.model.relational.Database; -import org.hibernate.boot.model.relational.ExportableProducer; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.boot.registry.classloading.spi.ClassLoadingException; import org.hibernate.boot.spi.MetadataBuildingContext; @@ -512,9 +512,12 @@ public void execute(SharedSessionContractImplementor session, Object incomingObj @Override public void registerExportables(Database database) { - if ( ExportableProducer.class.isInstance( subGenerator ) ) { - ( (ExportableProducer) subGenerator ).registerExportables( database ); - } + subGenerator.registerExportables( database ); + } + + @Override + public void initialize(SqlStringGenerationContext context) { + subGenerator.initialize( context ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Constraint.java b/hibernate-core/src/main/java/org/hibernate/mapping/Constraint.java index a15aa2a3a656..37d68b6f00fc 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Constraint.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Constraint.java @@ -18,6 +18,7 @@ import org.hibernate.HibernateException; import org.hibernate.boot.model.relational.Exportable; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.dialect.Dialect; import org.hibernate.engine.spi.Mapping; import org.hibernate.internal.util.StringHelper; @@ -179,9 +180,12 @@ public boolean isGenerated(Dialect dialect) { return true; } - public String sqlDropString(Dialect dialect, String defaultCatalog, String defaultSchema) { + @Override + public String sqlDropString(SqlStringGenerationContext context, + String defaultCatalog, String defaultSchema) { + Dialect dialect = context.getDialect(); if ( isGenerated( dialect ) ) { - final String tableName = getTable().getQualifiedName( dialect, defaultCatalog, defaultSchema ); + final String tableName = getTable().getQualifiedName( context ); return String.format( Locale.ROOT, "%s evictData constraint %s", @@ -194,14 +198,17 @@ public String sqlDropString(Dialect dialect, String defaultCatalog, String defau } } - public String sqlCreateString(Dialect dialect, Mapping p, String defaultCatalog, String defaultSchema) { + @Override + public String sqlCreateString(Mapping p, SqlStringGenerationContext context, String defaultCatalog, + String defaultSchema) { + Dialect dialect = context.getDialect(); if ( isGenerated( dialect ) ) { // Certain dialects (ex: HANA) don't support FKs as expected, but other constraints can still be created. // If that's the case, hasAlterTable() will be true, but getAddForeignKeyConstraintString will return // empty string. Prevent blank "alter table" statements. - String constraintString = sqlConstraintString( dialect, getName(), defaultCatalog, defaultSchema ); + String constraintString = sqlConstraintString( context, getName(), defaultCatalog, defaultSchema ); if ( !StringHelper.isEmpty( constraintString ) ) { - final String tableName = getTable().getQualifiedName( dialect, defaultCatalog, defaultSchema ); + final String tableName = getTable().getQualifiedName( context ); return dialect.getAlterTableString( tableName ) + " " + constraintString; } } @@ -213,7 +220,7 @@ public List getColumns() { } public abstract String sqlConstraintString( - Dialect d, + SqlStringGenerationContext context, String constraintName, String defaultCatalog, String defaultSchema); diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/ForeignKey.java b/hibernate-core/src/main/java/org/hibernate/mapping/ForeignKey.java index 73d891f1a647..ffd3dc23784d 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/ForeignKey.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/ForeignKey.java @@ -11,6 +11,7 @@ import java.util.List; import org.hibernate.MappingException; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.dialect.Dialect; import org.hibernate.internal.util.StringHelper; @@ -54,11 +55,13 @@ public void setName(String name) { } } + @Override public String sqlConstraintString( - Dialect dialect, + SqlStringGenerationContext context, String constraintName, String defaultCatalog, String defaultSchema) { + Dialect dialect = context.getDialect(); String[] columnNames = new String[getColumnSpan()]; String[] referencedColumnNames = new String[getColumnSpan()]; @@ -87,9 +90,7 @@ public String sqlConstraintString( constraintName, columnNames, referencedTable.getQualifiedName( - dialect, - defaultCatalog, - defaultSchema + context ), referencedColumnNames, isReferenceToPrimaryKey() @@ -172,8 +173,11 @@ public void setKeyDefinition(String keyDefinition) { this.keyDefinition = keyDefinition; } - public String sqlDropString(Dialect dialect, String defaultCatalog, String defaultSchema) { - String tableName = getTable().getQualifiedName( dialect, defaultCatalog, defaultSchema ); + @Override + public String sqlDropString(SqlStringGenerationContext context, + String defaultCatalog, String defaultSchema) { + Dialect dialect = context.getDialect(); + String tableName = getTable().getQualifiedName( context ); final StringBuilder buf = new StringBuilder( dialect.getAlterTableString( tableName ) ); buf.append( dialect.getDropForeignKeyString() ); if ( dialect.supportsIfExistsBeforeConstraintName() ) { diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Index.java b/hibernate-core/src/main/java/org/hibernate/mapping/Index.java index 4d7da83ec7cc..c0937b19ff58 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Index.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Index.java @@ -16,8 +16,8 @@ import org.hibernate.boot.Metadata; import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.relational.Exportable; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.dialect.Dialect; -import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.engine.spi.Mapping; import org.hibernate.internal.util.StringHelper; @@ -32,10 +32,13 @@ public class Index implements RelationalModel, Exportable, Serializable { private java.util.Map columnOrderMap = new HashMap( ); private Identifier name; - public String sqlCreateString(Dialect dialect, Mapping mapping, String defaultCatalog, String defaultSchema) + @Override + public String sqlCreateString(Mapping mapping, SqlStringGenerationContext context, String defaultCatalog, + String defaultSchema) throws HibernateException { + Dialect dialect = context.getDialect(); return buildSqlCreateIndexString( - dialect, + context, getQuotedName( dialect ), getTable(), getColumnIterator(), @@ -47,12 +50,12 @@ public String sqlCreateString(Dialect dialect, Mapping mapping, String defaultCa } public static String buildSqlDropIndexString( - Dialect dialect, + SqlStringGenerationContext context, Table table, String name, String defaultCatalog, String defaultSchema) { - return buildSqlDropIndexString( name, table.getQualifiedName( dialect, defaultCatalog, defaultSchema ) ); + return buildSqlDropIndexString( name, table.getQualifiedName( context ) ); } public static String buildSqlDropIndexString( @@ -62,7 +65,7 @@ public static String buildSqlDropIndexString( } public static String buildSqlCreateIndexString( - Dialect dialect, + SqlStringGenerationContext context, String name, Table table, Iterator columns, @@ -71,9 +74,9 @@ public static String buildSqlCreateIndexString( String defaultCatalog, String defaultSchema) { return buildSqlCreateIndexString( - dialect, + context.getDialect(), name, - table.getQualifiedName( dialect, defaultCatalog, defaultSchema ), + table.getQualifiedName( context ), columns, columnOrderMap, unique @@ -109,42 +112,17 @@ public static String buildSqlCreateIndexString( } public static String buildSqlCreateIndexString( - Dialect dialect, - String name, - Table table, - Iterator columns, - boolean unique, - String defaultCatalog, - String defaultSchema) { - return buildSqlCreateIndexString( - dialect, - name, - table, - columns, - Collections.EMPTY_MAP, - unique, - defaultCatalog, - defaultSchema - ); - } - - public static String buildSqlCreateIndexString( - Dialect dialect, + SqlStringGenerationContext context, String name, Table table, Iterator columns, java.util.Map columnOrderMap, boolean unique, Metadata metadata) { - final JdbcEnvironment jdbcEnvironment = metadata.getDatabase().getJdbcEnvironment(); - - final String tableName = jdbcEnvironment.getQualifiedObjectNameFormatter().format( - table.getQualifiedTableName(), - dialect - ); + final String tableName = context.format( table.getQualifiedTableName() ); return buildSqlCreateIndexString( - dialect, + context.getDialect(), name, tableName, columns, @@ -168,10 +146,12 @@ public String sqlConstraintString(Dialect dialect) { } @Override - public String sqlDropString(Dialect dialect, String defaultCatalog, String defaultSchema) { + public String sqlDropString(SqlStringGenerationContext context, + String defaultCatalog, String defaultSchema) { + Dialect dialect = context.getDialect(); return "drop index " + StringHelper.qualify( - table.getQualifiedName( dialect, defaultCatalog, defaultSchema ), + table.getQualifiedName( context ), getQuotedName( dialect ) ); } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/KeyValue.java b/hibernate-core/src/main/java/org/hibernate/mapping/KeyValue.java index 8635fb99f630..bda09a04c4db 100755 --- a/hibernate-core/src/main/java/org/hibernate/mapping/KeyValue.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/KeyValue.java @@ -18,6 +18,11 @@ */ public interface KeyValue extends Value { + /** + * @deprecated Use {@link #createIdentifierGenerator(IdentifierGeneratorFactory, Dialect, RootClass)} + * instead. + */ + @Deprecated public IdentifierGenerator createIdentifierGenerator( IdentifierGeneratorFactory identifierGeneratorFactory, Dialect dialect, @@ -25,6 +30,11 @@ public IdentifierGenerator createIdentifierGenerator( String defaultSchema, RootClass rootClass) throws MappingException; + IdentifierGenerator createIdentifierGenerator( + IdentifierGeneratorFactory identifierGeneratorFactory, + Dialect dialect, + RootClass rootClass) throws MappingException; + public boolean isIdentityColumn(IdentifierGeneratorFactory identifierGeneratorFactory, Dialect dialect); public void createForeignKeyOfEntity(String entityName); 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/PrimaryKey.java b/hibernate-core/src/main/java/org/hibernate/mapping/PrimaryKey.java index 3c5e9a03b127..162c37f19855 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/PrimaryKey.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/PrimaryKey.java @@ -7,6 +7,7 @@ package org.hibernate.mapping; import java.util.Iterator; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.dialect.Dialect; import org.hibernate.internal.util.StringHelper; @@ -69,7 +70,9 @@ public String sqlConstraintString(Dialect dialect) { return buf.append(')').toString(); } - public String sqlConstraintString(Dialect dialect, String constraintName, String defaultCatalog, String defaultSchema) { + @Override + public String sqlConstraintString(SqlStringGenerationContext context, String constraintName, String defaultCatalog, String defaultSchema) { + Dialect dialect = context.getDialect(); StringBuilder buf = new StringBuilder( dialect.getAddPrimaryKeyConstraintString(constraintName) ).append('('); 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 167a28bc1a72..79b68f4ec1e6 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/RelationalModel.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/RelationalModel.java @@ -6,6 +6,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 { - String sqlCreateString(Dialect dialect, Mapping p, String defaultCatalog, String defaultSchema) throws HibernateException; - String sqlDropString(Dialect dialect, String defaultCatalog, String defaultSchema); + @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/mapping/SimpleValue.java b/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java index da3afbe33ea5..97fac7f3ab61 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java @@ -52,13 +52,9 @@ import org.hibernate.type.descriptor.converter.AttributeConverterSqlTypeDescriptorAdapter; import org.hibernate.type.descriptor.converter.AttributeConverterTypeAdapter; import org.hibernate.type.descriptor.java.BasicJavaDescriptor; -import org.hibernate.type.descriptor.java.JavaTypeDescriptor; -import org.hibernate.type.descriptor.spi.JdbcRecommendedSqlTypeMappingContext; -import org.hibernate.type.descriptor.sql.JdbcTypeJavaClassMappings; import org.hibernate.type.descriptor.sql.LobTypeMappings; import org.hibernate.type.descriptor.sql.NationalizedTypeMappings; import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; -import org.hibernate.type.spi.TypeConfiguration; import org.hibernate.usertype.DynamicParameterizedType; /** @@ -276,6 +272,14 @@ public IdentifierGenerator getIdentifierGenerator() { return identifierGenerator; } + @Override + public IdentifierGenerator createIdentifierGenerator( + IdentifierGeneratorFactory identifierGeneratorFactory, + Dialect dialect, + RootClass rootClass) throws MappingException { + return createIdentifierGenerator( identifierGeneratorFactory, dialect, null, null, rootClass ); + } + @Override public IdentifierGenerator createIdentifierGenerator( IdentifierGeneratorFactory identifierGeneratorFactory, @@ -289,18 +293,17 @@ public IdentifierGenerator createIdentifierGenerator( } Properties params = new Properties(); - - //if the hibernate-mapping did not specify a schema/catalog, use the defaults - //specified by properties - but note that if the schema/catalog were specified - //in hibernate-mapping, or as params, they will already be initialized and - //will override the values set here (they are in identifierGeneratorProperties) + + // This is for backwards compatibility only; + // when this method is called by Hibernate ORM, defaultSchema and defaultCatalog are always + // null, and defaults are handled later. if ( defaultSchema!=null ) { params.setProperty(PersistentIdentifierGenerator.SCHEMA, defaultSchema); } if ( defaultCatalog!=null ) { params.setProperty(PersistentIdentifierGenerator.CATALOG, defaultCatalog); } - + //pass the entity-name, if not a collection-id if (rootClass!=null) { params.setProperty( IdentifierGenerator.ENTITY_NAME, rootClass.getEntityName() ); diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Table.java b/hibernate-core/src/main/java/org/hibernate/mapping/Table.java index afe61a784ffb..827058d65202 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Table.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Table.java @@ -16,6 +16,7 @@ import java.util.Locale; import java.util.Map; import java.util.Objects; +import java.util.function.Function; import org.hibernate.HibernateException; import org.hibernate.MappingException; @@ -25,9 +26,8 @@ import org.hibernate.boot.model.relational.InitCommand; import org.hibernate.boot.model.relational.Namespace; import org.hibernate.boot.model.relational.QualifiedTableName; +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.env.spi.QualifiedObjectNameFormatter; import org.hibernate.engine.spi.Mapping; import org.hibernate.tool.hbm2ddl.ColumnMetadata; import org.hibernate.tool.hbm2ddl.TableMetadata; @@ -67,7 +67,7 @@ public class Table implements RelationalModel, Serializable, Exportable { private boolean hasDenormalizedTables; private String comment; - private List initCommands; + private List> initCommandProducers; public Table() { } @@ -112,28 +112,16 @@ public Table(Namespace namespace, String subselect, boolean isAbstract) { this.isAbstract = isAbstract; } - /** - * @deprecated Should use {@link QualifiedObjectNameFormatter#format} on QualifiedObjectNameFormatter - * obtained from {@link org.hibernate.engine.jdbc.env.spi.JdbcEnvironment} - */ - @Deprecated - public String getQualifiedName(Dialect dialect, String defaultCatalog, String defaultSchema) { + public String getQualifiedName(SqlStringGenerationContext context) { if ( subselect != null ) { return "( " + subselect + " )"; } - String quotedName = getQuotedName( dialect ); - String usedSchema = schema == null ? - defaultSchema : - getQuotedSchema( dialect ); - String usedCatalog = catalog == null ? - defaultCatalog : - getQuotedCatalog( dialect ); - return qualify( usedCatalog, usedSchema, quotedName ); + return context.format( new QualifiedTableName( catalog, schema, name ) ); } /** - * @deprecated Should use {@link QualifiedObjectNameFormatter#format} on QualifiedObjectNameFormatter - * obtained from {@link org.hibernate.engine.jdbc.env.spi.JdbcEnvironment} + * @deprecated Should build a {@link QualifiedTableName} + * then use {@link SqlStringGenerationContext#format(QualifiedTableName)}. */ @Deprecated public static String qualify(String catalog, String schema, String table) { @@ -445,19 +433,8 @@ public Iterator sqlAlterStrings( Dialect dialect, Metadata metadata, TableInformation tableInfo, - Identifier defaultCatalog, - Identifier defaultSchema) throws HibernateException { - - final JdbcEnvironment jdbcEnvironment = metadata.getDatabase().getJdbcEnvironment(); - - final String tableName = jdbcEnvironment.getQualifiedObjectNameFormatter().format( - new QualifiedTableName( - catalog != null ? catalog : defaultCatalog, - schema != null ? schema : defaultSchema, - name - ), - dialect - ); + SqlStringGenerationContext sqlStringGenerationContext) throws HibernateException { + final String tableName = sqlStringGenerationContext.format( new QualifiedTableName( catalog, schema, name ) ); StringBuilder root = new StringBuilder( dialect.getAlterTableString( tableName ) ) .append( ' ' ) @@ -495,7 +472,7 @@ public Iterator sqlAlterStrings( UniqueKey uk = getOrCreateUniqueKey( keyName ); uk.addColumn( column ); alter.append( dialect.getUniqueDelegate() - .getColumnDefinitionUniquenessFragment( column ) ); + .getColumnDefinitionUniquenessFragment( column, sqlStringGenerationContext ) ); } if ( column.hasCheckConstraint() && dialect.supportsColumnCheck() ) { @@ -527,10 +504,13 @@ public boolean hasPrimaryKey() { return getPrimaryKey() != null; } - public String sqlCreateString(Dialect dialect, Mapping p, String defaultCatalog, String defaultSchema) { + @Override + public String sqlCreateString(Mapping p, SqlStringGenerationContext context, String defaultCatalog, + String defaultSchema) { + Dialect dialect = context.getDialect(); StringBuilder buf = new StringBuilder( hasPrimaryKey() ? dialect.getCreateTableString() : dialect.getCreateMultisetTableString() ) .append( ' ' ) - .append( getQualifiedName( dialect, defaultCatalog, defaultSchema ) ) + .append( getQualifiedName( context ) ) .append( " (" ); boolean identityColumn = idValue != null && idValue.isIdentityColumn( p.getIdentifierGeneratorFactory(), dialect ); @@ -579,7 +559,7 @@ public String sqlCreateString(Dialect dialect, Mapping p, String defaultCatalog, UniqueKey uk = getOrCreateUniqueKey( keyName ); uk.addColumn( col ); buf.append( dialect.getUniqueDelegate() - .getColumnDefinitionUniquenessFragment( col ) ); + .getColumnDefinitionUniquenessFragment( col, context ) ); } if ( col.hasCheckConstraint() && dialect.supportsColumnCheck() ) { @@ -603,7 +583,7 @@ public String sqlCreateString(Dialect dialect, Mapping p, String defaultCatalog, .append( getPrimaryKey().sqlConstraintString( dialect ) ); } - buf.append( dialect.getUniqueDelegate().getTableCreationUniqueConstraintsFragment( this ) ); + buf.append( dialect.getUniqueDelegate().getTableCreationUniqueConstraintsFragment( this, context ) ); if ( dialect.supportsTableCheck() ) { for ( String checkConstraint : checkConstraints ) { @@ -622,8 +602,10 @@ public String sqlCreateString(Dialect dialect, Mapping p, String defaultCatalog, return buf.append( dialect.getTableTypeString() ).toString(); } - public String sqlDropString(Dialect dialect, String defaultCatalog, String defaultSchema) { - return dialect.getDropTableString( getQualifiedName( dialect, defaultCatalog, defaultSchema ) ); + @Override + public String sqlDropString(SqlStringGenerationContext context, String defaultCatalog, String defaultSchema) { + Dialect dialect = context.getDialect(); + return dialect.getDropTableString( getQualifiedName( context ) ); } public PrimaryKey getPrimaryKey() { @@ -829,25 +811,6 @@ public Iterator getCheckConstraintsIterator() { return checkConstraints.iterator(); } - public Iterator sqlCommentStrings(Dialect dialect, String defaultCatalog, String defaultSchema) { - List comments = new ArrayList<>(); - if ( dialect.supportsCommentOn() ) { - String tableName = getQualifiedName( dialect, defaultCatalog, defaultSchema ); - if ( comment != null ) { - comments.add( "comment on table " + tableName + " is '" + comment + "'" ); - } - Iterator iter = getColumnIterator(); - while ( iter.hasNext() ) { - Column column = (Column) iter.next(); - String columnComment = column.getComment(); - if ( columnComment != null ) { - comments.add( "comment on column " + tableName + '.' + column.getQuotedName( dialect ) + " is '" + columnComment + "'" ); - } - } - } - return comments.iterator(); - } - @Override public String getExportIdentifier() { return Table.qualify( @@ -897,18 +860,30 @@ public String toString() { } } + /** + * @deprecated Use {@link #addInitCommand(Function)} instead. + */ + @Deprecated public void addInitCommand(InitCommand command) { - if ( initCommands == null ) { - initCommands = new ArrayList<>(); + addInitCommand( ignored -> command ); + } + + public void addInitCommand(Function commandProducer) { + if ( initCommandProducers == null ) { + initCommandProducers = new ArrayList<>(); } - initCommands.add( command ); + initCommandProducers.add( commandProducer ); } - public List getInitCommands() { - if ( initCommands == null ) { + public List getInitCommands(SqlStringGenerationContext context) { + if ( initCommandProducers == null ) { return Collections.emptyList(); } else { + List initCommands = new ArrayList<>(); + for ( Function producer : initCommandProducers ) { + initCommands.add( producer.apply( context ) ); + } return Collections.unmodifiableList( initCommands ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/UniqueKey.java b/hibernate-core/src/main/java/org/hibernate/mapping/UniqueKey.java index b3ff9fdf5d0c..9ad096c2b562 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/UniqueKey.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/UniqueKey.java @@ -9,6 +9,7 @@ import java.util.HashMap; import java.util.Map; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.dialect.Dialect; import org.hibernate.engine.spi.Mapping; import org.hibernate.internal.util.StringHelper; @@ -23,7 +24,7 @@ public class UniqueKey extends Constraint { @Override public String sqlConstraintString( - Dialect dialect, + SqlStringGenerationContext context, String constraintName, String defaultCatalog, String defaultSchema) { @@ -34,9 +35,8 @@ public String sqlConstraintString( @Override public String sqlCreateString( - Dialect dialect, Mapping p, - String defaultCatalog, + SqlStringGenerationContext context, String defaultCatalog, String defaultSchema) { return null; // return dialect.getUniqueDelegate().getAlterTableToAddUniqueKeyCommand( @@ -46,8 +46,7 @@ public String sqlCreateString( @Override public String sqlDropString( - Dialect dialect, - String defaultCatalog, + SqlStringGenerationContext context, String defaultCatalog, String defaultSchema) { return null; // return dialect.getUniqueDelegate().getAlterTableToDropUniqueKeyCommand( 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 055485700e8d..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,27 +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 ) { - 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/collection/AbstractCollectionPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java index 9c5ba874c7cd..81c5ec033306 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java @@ -240,7 +240,6 @@ public AbstractCollectionPersister( PersisterCreationContext creationContext) throws MappingException, CacheException { final Database database = creationContext.getMetadata().getDatabase(); - final JdbcEnvironment jdbcEnvironment = database.getJdbcEnvironment(); this.factory = creationContext.getSessionFactory(); this.cacheAccessStrategy = cacheAccessStrategy; @@ -272,7 +271,7 @@ public AbstractCollectionPersister( isArray = collectionBinding.isArray(); subselectLoadable = collectionBinding.isSubselectLoadable(); - qualifiedTableName = determineTableName( table, jdbcEnvironment ); + qualifiedTableName = determineTableName( table ); int spacesSize = 1 + collectionBinding.getSynchronizedTables().size(); spaces = new String[spacesSize]; @@ -455,10 +454,9 @@ public AbstractCollectionPersister( identifierGenerator = idColl.getIdentifier().createIdentifierGenerator( creationContext.getMetadata().getIdentifierGeneratorFactory(), factory.getDialect(), - factory.getSettings().getDefaultCatalogName(), - factory.getSettings().getDefaultSchemaName(), null ); + identifierGenerator.initialize( creationContext.getSessionFactory().getSqlStringGenerationContext() ); } else { identifierType = null; @@ -614,15 +612,12 @@ else if ( !elementType.isEntityType() ) { initCollectionPropertyMap(); } - protected String determineTableName(Table table, JdbcEnvironment jdbcEnvironment) { + protected String determineTableName(Table table) { if ( table.getSubselect() != null ) { return "( " + table.getSubselect() + " )"; } - return jdbcEnvironment.getQualifiedObjectNameFormatter().format( - table.getQualifiedTableName(), - jdbcEnvironment.getDialect() - ); + return factory.getSqlStringGenerationContext().format( table.getQualifiedTableName() ); } private class ColumnMapperImpl implements ColumnMapper { 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 fbd6be954474..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 @@ -37,6 +37,7 @@ import org.hibernate.Session; import org.hibernate.StaleObjectStateException; import org.hibernate.StaleStateException; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.boot.spi.SessionFactoryOptions; import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; @@ -65,7 +66,6 @@ import org.hibernate.engine.internal.StatefulPersistenceContext; import org.hibernate.engine.internal.Versioning; import org.hibernate.engine.jdbc.batch.internal.BasicBatchKey; -import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.engine.jdbc.spi.JdbcCoordinator; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.CachedNaturalIdValueSource; @@ -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; @@ -2957,8 +2957,8 @@ else if ( includeProperty[i] ) { * * @return The insert SQL statement string */ - public String generateIdentityInsertString(boolean[] includeProperty) { - Insert insert = identityDelegate.prepareIdentifierGeneratingInsert(); + public String generateIdentityInsertString(SqlStringGenerationContext context, boolean[] includeProperty) { + Insert insert = identityDelegate.prepareIdentifierGeneratingInsert( context ); insert.setTableName( getTableName( 0 ) ); // add normal properties except lobs @@ -3297,7 +3297,7 @@ public String getSelectByUniqueKeyString(String propertyName) { .toStatementString(); } - private BasicBatchKey inserBatchKey; + private BasicBatchKey insertBatchKey; /** * Perform an SQL INSERT. @@ -3338,8 +3338,8 @@ public void insert( jdbcBatchSizeToUse > 1 && getIdentifierGenerator().supportsJdbcBatchInserts(); - if ( useBatch && inserBatchKey == null ) { - inserBatchKey = new BasicBatchKey( + if ( useBatch && insertBatchKey == null ) { + insertBatchKey = new BasicBatchKey( getEntityName() + "#INSERT", expectation ); @@ -3352,7 +3352,7 @@ public void insert( if ( useBatch ) { insert = session .getJdbcCoordinator() - .getBatch( inserBatchKey ) + .getBatch( insertBatchKey ) .getBatchStatement( sql, callable ); } else { @@ -3372,7 +3372,7 @@ public void insert( dehydrate( id, fields, null, notNull, propertyColumnInsertable, j, insert, session, index, false ); if ( useBatch ) { - session.getJdbcCoordinator().getBatch( inserBatchKey ).addToBatch(); + session.getJdbcCoordinator().getBatch( insertBatchKey ).addToBatch(); } else { expectation.verifyOutcome( @@ -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 @@ -4396,7 +4425,7 @@ private void doLateInit() { identityDelegate = ( (PostInsertIdentifierGenerator) getIdentifierGenerator() ) .getInsertGeneratedIdentifierDelegate( this, getFactory().getDialect(), useGetGeneratedKeys() ); sqlIdentityInsertString = customSQLInsert[0] == null - ? generateIdentityInsertString( getPropertyInsertability() ) + ? generateIdentityInsertString( factory.getSqlStringGenerationContext(), getPropertyInsertability() ) : substituteBrackets( customSQLInsert[0] ); } else { @@ -5045,6 +5074,14 @@ public IdentifierGenerator getIdentifierGenerator() throws HibernateException { return entityMetamodel.getIdentifierProperty().getIdentifierGenerator(); } + /** + * @deprecated Exposed for tests only + */ + @Deprecated + public InsertGeneratedIdentifierDelegate getIdentityDelegate() { + return identityDelegate; + } + public String getRootEntityName() { return entityMetamodel.getRootName(); } @@ -5802,15 +5839,12 @@ public int determineTableNumberForColumn(String columnName) { return 0; } - protected String determineTableName(Table table, JdbcEnvironment jdbcEnvironment) { + protected String determineTableName(Table table) { if ( table.getSubselect() != null ) { return "( " + table.getSubselect() + " )"; } - return jdbcEnvironment.getQualifiedObjectNameFormatter().format( - table.getQualifiedTableName(), - jdbcEnvironment.getDialect() - ); + return factory.getSqlStringGenerationContext().format( table.getQualifiedTableName() ); } @Override 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/persister/entity/JoinedSubclassEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/JoinedSubclassEntityPersister.java index fa7f5cb30d88..64da81c8e03a 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/JoinedSubclassEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/JoinedSubclassEntityPersister.java @@ -13,7 +13,6 @@ import org.hibernate.boot.model.relational.Database; import org.hibernate.cache.spi.access.EntityDataAccess; import org.hibernate.cache.spi.access.NaturalIdDataAccess; -import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.internal.DynamicFilterAliasGenerator; @@ -136,7 +135,6 @@ public JoinedSubclassEntityPersister( final SessionFactoryImplementor factory = creationContext.getSessionFactory(); final Database database = creationContext.getMetadata().getDatabase(); - final JdbcEnvironment jdbcEnvironment = database.getJdbcEnvironment(); // DISCRIMINATOR @@ -215,7 +213,7 @@ else if ( persistentClass.isDiscriminatorValueNotNull() ) { while ( tItr.hasNext() ) { final Table table = (Table) tItr.next(); final KeyValue key = (KeyValue) kItr.next(); - final String tableName = determineTableName( table, jdbcEnvironment ); + final String tableName = determineTableName( table ); tableNames.add( tableName ); String[] keyCols = new String[idColumnSpan]; String[] keyColReaders = new String[idColumnSpan]; @@ -248,7 +246,7 @@ else if ( persistentClass.isDiscriminatorValueNotNull() ) { isInverseTable[tableIndex] = join.isInverse(); Table table = join.getTable(); - final String tableName = determineTableName( table, jdbcEnvironment ); + final String tableName = determineTableName( table ); tableNames.add( tableName ); KeyValue key = join.getKey(); @@ -294,7 +292,7 @@ else if ( persistentClass.isDiscriminatorValueNotNull() ) { isLazies.add( Boolean.FALSE ); isInverses.add( Boolean.FALSE ); isNullables.add( Boolean.FALSE ); - final String tableName = determineTableName( tab, jdbcEnvironment ); + final String tableName = determineTableName( tab ); subclassTableNames.add( tableName ); String[] key = new String[idColumnSpan]; Iterator cItr = tab.getPrimaryKey().getColumnIterator(); @@ -316,7 +314,7 @@ else if ( persistentClass.isDiscriminatorValueNotNull() ) { isNullables.add( join.isOptional() ); isLazies.add( join.isLazy() ); - String joinTableName = determineTableName( joinTable, jdbcEnvironment ); + String joinTableName = determineTableName( joinTable ); subclassTableNames.add( joinTableName ); String[] key = new String[idColumnSpan]; Iterator citer = joinTable.getPrimaryKey().getColumnIterator(); @@ -440,9 +438,7 @@ else if ( persistentClass.isDiscriminatorValueNotNull() ) { while ( iter.hasNext() ) { Property prop = (Property) iter.next(); String tabname = prop.getValue().getTable().getQualifiedName( - factory.getDialect(), - factory.getSettings().getDefaultCatalogName(), - factory.getSettings().getDefaultSchemaName() + factory.getSqlStringGenerationContext() ); propertyTableNumbers[i] = getTableId( tabname, this.tableNames ); naturalOrderPropertyTableNumbers[i] = getTableId( tabname, naturalOrderTableNames ); @@ -462,9 +458,7 @@ else if ( persistentClass.isDiscriminatorValueNotNull() ) { Property prop = (Property) iter.next(); Table tab = prop.getValue().getTable(); String tabname = tab.getQualifiedName( - factory.getDialect(), - factory.getSettings().getDefaultCatalogName(), - factory.getSettings().getDefaultSchemaName() + factory.getSqlStringGenerationContext() ); Integer tabnum = getTableId( tabname, subclassTableNameClosure ); propTableNumbers.add( tabnum ); @@ -498,9 +492,7 @@ else if ( persistentClass.isDiscriminatorValueNotNull() ) { notNullColumnTableNumbers = new int[subclassSpan]; final int id = getTableId( persistentClass.getTable().getQualifiedName( - factory.getDialect(), - factory.getSettings().getDefaultCatalogName(), - factory.getSettings().getDefaultSchemaName() + factory.getSqlStringGenerationContext() ), subclassTableNameClosure ); @@ -552,9 +544,7 @@ else if ( sc.isDiscriminatorValueNotNull() ) { discriminatorValues[k] = discriminatorValue.toString(); int id = getTableId( sc.getTable().getQualifiedName( - factory.getDialect(), - factory.getSettings().getDefaultCatalogName(), - factory.getSettings().getDefaultSchemaName() + factory.getSqlStringGenerationContext() ), subclassTableNameClosure ); @@ -677,9 +667,7 @@ private void associateSubclassNamesToSubclassTableIndexes( SessionFactoryImplementor factory) { final String tableName = persistentClass.getTable().getQualifiedName( - factory.getDialect(), - factory.getSettings().getDefaultCatalogName(), - factory.getSettings().getDefaultSchemaName() + factory.getSqlStringGenerationContext() ); associateSubclassNamesToSubclassTableIndex( tableName, classNames, mapping ); @@ -688,9 +676,7 @@ private void associateSubclassNamesToSubclassTableIndexes( while ( itr.hasNext() ) { final Join join = (Join) itr.next(); final String secondaryTableName = join.getTable().getQualifiedName( - factory.getDialect(), - factory.getSettings().getDefaultCatalogName(), - factory.getSettings().getDefaultSchemaName() + factory.getSqlStringGenerationContext() ); associateSubclassNamesToSubclassTableIndex( secondaryTableName, classNames, mapping ); } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/SingleTableEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/SingleTableEntityPersister.java index 566d8d5a49c0..33a9db1d43a1 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/SingleTableEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/SingleTableEntityPersister.java @@ -128,7 +128,6 @@ public SingleTableEntityPersister( final SessionFactoryImplementor factory = creationContext.getSessionFactory(); final Database database = creationContext.getMetadata().getDatabase(); - final JdbcEnvironment jdbcEnvironment = database.getJdbcEnvironment(); // CLASS + TABLE @@ -138,7 +137,7 @@ public SingleTableEntityPersister( isNullableTable = new boolean[joinSpan]; keyColumnNames = new String[joinSpan][]; final Table table = persistentClass.getRootTable(); - qualifiedTableNames[0] = determineTableName( table, jdbcEnvironment ); + qualifiedTableNames[0] = determineTableName( table ); isInverseTable[0] = false; isNullableTable[0] = false; @@ -178,7 +177,7 @@ public SingleTableEntityPersister( int j = 1; while ( joinIter.hasNext() ) { Join join = (Join) joinIter.next(); - qualifiedTableNames[j] = determineTableName( join.getTable(), jdbcEnvironment ); + qualifiedTableNames[j] = determineTableName( join.getTable() ); isInverseTable[j] = join.isInverse(); isNullableTable[j] = join.isOptional(); cascadeDeleteEnabled[j] = join.getKey().isCascadeDeleteEnabled() && @@ -255,7 +254,7 @@ public SingleTableEntityPersister( isDeferreds.add( isDeferred ); hasDeferred |= isDeferred; - String joinTableName = determineTableName( join.getTable(), jdbcEnvironment ); + String joinTableName = determineTableName( join.getTable() ); subclassTables.add( joinTableName ); Iterator iter = join.getKey().getColumnIterator(); diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/UnionSubclassEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/UnionSubclassEntityPersister.java index 820383b9c6fa..5d50cca35865 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/UnionSubclassEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/UnionSubclassEntityPersister.java @@ -20,11 +20,11 @@ import org.hibernate.LockMode; import org.hibernate.MappingException; import org.hibernate.boot.model.relational.Database; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.cache.spi.access.EntityDataAccess; import org.hibernate.cache.spi.access.NaturalIdDataAccess; import org.hibernate.cfg.Settings; import org.hibernate.dialect.Dialect; -import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle; import org.hibernate.engine.spi.Mapping; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -85,11 +85,10 @@ public UnionSubclassEntityPersister( final SessionFactoryImplementor factory = creationContext.getSessionFactory(); final Database database = creationContext.getMetadata().getDatabase(); - final JdbcEnvironment jdbcEnvironment = database.getJdbcEnvironment(); // TABLE - tableName = determineTableName( persistentClass.getTable(), jdbcEnvironment ); + tableName = determineTableName( persistentClass.getTable() ); //Custom SQL @@ -168,7 +167,7 @@ public UnionSubclassEntityPersister( HashSet subclassTables = new HashSet(); Iterator subclassTableIter = persistentClass.getSubclassTableClosureIterator(); while ( subclassTableIter.hasNext() ) { - subclassTables.add( determineTableName( subclassTableIter.next(), jdbcEnvironment ) ); + subclassTables.add( determineTableName( subclassTableIter.next() ) ); } subclassSpaces = ArrayHelper.toStringArray( subclassTables ); @@ -182,7 +181,7 @@ public UnionSubclassEntityPersister( while ( tableIter.hasNext() ) { Table tab = tableIter.next(); if ( !tab.isAbstractUnionTable() ) { - final String tableName = determineTableName( tab, jdbcEnvironment ); + final String tableName = determineTableName( tab ); tableNames.add( tableName ); String[] key = new String[idColumnSpan]; Iterator citer = tab.getPrimaryKey().getColumnIterator(); @@ -363,12 +362,11 @@ protected String generateSubquery(PersistentClass model, Mapping mapping) { Dialect dialect = getFactory().getDialect(); Settings settings = getFactory().getSettings(); + SqlStringGenerationContext sqlStringGenerationContext = getFactory().getSqlStringGenerationContext(); if ( !model.hasSubclasses() ) { return model.getTable().getQualifiedName( - dialect, - settings.getDefaultCatalogName(), - settings.getDefaultSchemaName() + sqlStringGenerationContext ); } @@ -414,9 +412,7 @@ protected String generateSubquery(PersistentClass model, Mapping mapping) { buf.append( " from " ) .append( table.getQualifiedName( - dialect, - settings.getDefaultCatalogName(), - settings.getDefaultSchemaName() + sqlStringGenerationContext ) ); buf.append( " union " ); 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 bd6dc598adf5..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; @@ -84,24 +86,32 @@ public AbstractInformationExtractorImpl(ExtractionContext extractionContext) { "" ) ); + final List physicalTableTypesList = new ArrayList<>(); if ( ! StringHelper.isBlank( extraPhysicalTableTypesConfig ) ) { - this.extraPhysicalTableTypes = StringHelper.splitTrimmingTokens( - ",;", - extraPhysicalTableTypesConfig, - false + Collections.addAll( + physicalTableTypesList, + StringHelper.splitTrimmingTokens( + ",;", + extraPhysicalTableTypesConfig, + false + ) ); } + 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" ); } - if ( extraPhysicalTableTypes != null ) { - Collections.addAll( tableTypesList, extraPhysicalTableTypes ); - } - extractionContext.getJdbcEnvironment().getDialect().augmentRecognizedTableTypes( tableTypesList ); + Collections.addAll( tableTypesList, extraPhysicalTableTypes ); + 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/DatabaseInformationImpl.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/internal/DatabaseInformationImpl.java index 9af4d368df98..f2ad3077ad60 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/internal/DatabaseInformationImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/internal/DatabaseInformationImpl.java @@ -14,6 +14,7 @@ import org.hibernate.boot.model.relational.Namespace; import org.hibernate.boot.model.relational.QualifiedSequenceName; import org.hibernate.boot.model.relational.QualifiedTableName; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.resource.transaction.spi.DdlTransactionIsolator; import org.hibernate.service.ServiceRegistry; @@ -31,6 +32,7 @@ public class DatabaseInformationImpl implements DatabaseInformation, ExtractionContext.DatabaseObjectAccess { private final JdbcEnvironment jdbcEnvironment; + private final SqlStringGenerationContext sqlStringGenerationContext; private final ExtractionContext extractionContext; private final InformationExtractor extractor; @@ -39,16 +41,16 @@ public class DatabaseInformationImpl public DatabaseInformationImpl( ServiceRegistry serviceRegistry, JdbcEnvironment jdbcEnvironment, + SqlStringGenerationContext sqlStringGenerationContext, DdlTransactionIsolator ddlTransactionIsolator, - Namespace.Name defaultNamespace, SchemaManagementTool tool) throws SQLException { this.jdbcEnvironment = jdbcEnvironment; + this.sqlStringGenerationContext = sqlStringGenerationContext; this.extractionContext = tool.getExtractionTool().createExtractionContext( serviceRegistry, jdbcEnvironment, + sqlStringGenerationContext, ddlTransactionIsolator, - defaultNamespace.getCatalog(), - defaultNamespace.getSchema(), this ); @@ -78,12 +80,13 @@ private void initializeSequences() throws SQLException { @Override public boolean catalogExists(Identifier catalog) { - return extractor.catalogExists( catalog ); + return extractor.catalogExists( sqlStringGenerationContext.catalogWithDefault( catalog ) ); } @Override public boolean schemaExists(Namespace.Name namespace) { - return extractor.schemaExists( namespace.getCatalog(), namespace.getSchema() ); + return extractor.schemaExists( sqlStringGenerationContext.catalogWithDefault( namespace.getCatalog() ), + sqlStringGenerationContext.schemaWithDefault( namespace.getSchema() ) ); } @Override @@ -108,15 +111,16 @@ public TableInformation getTableInformation(QualifiedTableName tableName) { } return extractor.getTable( - tableName.getCatalogName(), - tableName.getSchemaName(), + sqlStringGenerationContext.catalogWithDefault( tableName.getCatalogName() ), + sqlStringGenerationContext.schemaWithDefault( tableName.getSchemaName() ), tableName.getTableName() ); } @Override public NameSpaceTablesInformation getTablesInformation(Namespace namespace) { - return extractor.getTables( namespace.getPhysicalName().getCatalog(), namespace.getPhysicalName().getSchema() ); + return extractor.getTables( sqlStringGenerationContext.catalogWithDefault( namespace.getPhysicalName().getCatalog() ), + sqlStringGenerationContext.schemaWithDefault( namespace.getPhysicalName().getSchema() ) ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/internal/ExtractionContextImpl.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/internal/ExtractionContextImpl.java index 9da18060d212..2dd8d5365347 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/internal/ExtractionContextImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/internal/ExtractionContextImpl.java @@ -11,6 +11,7 @@ import java.sql.SQLException; import org.hibernate.boot.model.naming.Identifier; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.service.ServiceRegistry; @@ -22,10 +23,9 @@ public class ExtractionContextImpl implements ExtractionContext { private final ServiceRegistry serviceRegistry; private final JdbcEnvironment jdbcEnvironment; + private final SqlStringGenerationContext sqlStringGenerationContext; private final JdbcConnectionAccess jdbcConnectionAccess; private final DatabaseObjectAccess registeredTableAccess; - private final Identifier defaultCatalogName; - private final Identifier defaultSchemaName; private Connection jdbcConnection; private DatabaseMetaData jdbcDatabaseMetaData; @@ -33,16 +33,14 @@ public class ExtractionContextImpl implements ExtractionContext { public ExtractionContextImpl( ServiceRegistry serviceRegistry, JdbcEnvironment jdbcEnvironment, + SqlStringGenerationContext sqlStringGenerationContext, JdbcConnectionAccess jdbcConnectionAccess, - DatabaseObjectAccess registeredTableAccess, - Identifier defaultCatalogName, - Identifier defaultSchemaName) { + DatabaseObjectAccess registeredTableAccess) { this.serviceRegistry = serviceRegistry; this.jdbcEnvironment = jdbcEnvironment; + this.sqlStringGenerationContext = sqlStringGenerationContext; this.jdbcConnectionAccess = jdbcConnectionAccess; this.registeredTableAccess = registeredTableAccess; - this.defaultCatalogName = defaultCatalogName; - this.defaultSchemaName = defaultSchemaName; } @Override @@ -55,6 +53,11 @@ public JdbcEnvironment getJdbcEnvironment() { return jdbcEnvironment; } + @Override + public SqlStringGenerationContext getSqlStringGenerationContext() { + return sqlStringGenerationContext; + } + @Override public Connection getJdbcConnection() { if ( jdbcConnection == null ) { @@ -83,12 +86,12 @@ public DatabaseMetaData getJdbcDatabaseMetaData() { @Override public Identifier getDefaultCatalog() { - return defaultCatalogName; + return sqlStringGenerationContext.getDefaultCatalog(); } @Override public Identifier getDefaultSchema() { - return defaultSchemaName; + return sqlStringGenerationContext.getDefaultSchema(); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/internal/InformationExtractorJdbcDatabaseMetaDataImpl.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/internal/InformationExtractorJdbcDatabaseMetaDataImpl.java index 25d9f9c03e88..0f934484ccf5 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/internal/InformationExtractorJdbcDatabaseMetaDataImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/internal/InformationExtractorJdbcDatabaseMetaDataImpl.java @@ -137,11 +137,10 @@ protected void addColumns(TableInformation tableInformation) { final ExtractionContext extractionContext = getExtractionContext(); // We use this dummy query to retrieve the table information through the ResultSetMetaData // This is significantly better than to use the DatabaseMetaData especially on Oracle with synonyms enable - final String tableName = extractionContext.getJdbcEnvironment().getQualifiedObjectNameFormatter().format( + final String tableName = extractionContext.getSqlStringGenerationContext().format( // The name comes from the database, so the case is correct // But we quote here to avoid issues with reserved words - tableInformation.getName().quote(), - extractionContext.getJdbcEnvironment().getDialect() + tableInformation.getName().quote() ); try { 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/extract/spi/ExtractionContext.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/spi/ExtractionContext.java index ab1c7c4d597d..ddd2d52e3ae7 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/spi/ExtractionContext.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/spi/ExtractionContext.java @@ -16,6 +16,7 @@ import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.relational.QualifiedSequenceName; import org.hibernate.boot.model.relational.QualifiedTableName; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.service.ServiceRegistry; @@ -30,6 +31,7 @@ public interface ExtractionContext { ServiceRegistry getServiceRegistry(); JdbcEnvironment getJdbcEnvironment(); + SqlStringGenerationContext getSqlStringGenerationContext(); Connection getJdbcConnection(); DatabaseMetaData getJdbcDatabaseMetaData(); @@ -83,6 +85,11 @@ public JdbcEnvironment getJdbcEnvironment() { return null; } + @Override + public SqlStringGenerationContext getSqlStringGenerationContext() { + return null; + } + @Override public Connection getJdbcConnection() { return null; 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 6acbc9fe73e5..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 @@ -22,10 +22,13 @@ import org.hibernate.boot.model.relational.Exportable; import org.hibernate.boot.model.relational.Namespace; import org.hibernate.boot.model.relational.Sequence; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; +import org.hibernate.boot.model.relational.internal.SqlStringGenerationContextImpl; import org.hibernate.boot.spi.MetadataImplementor; 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.JdbcEnvironment; import org.hibernate.engine.jdbc.internal.FormatStyle; import org.hibernate.engine.jdbc.internal.Formatter; import org.hibernate.internal.util.StringHelper; @@ -89,6 +92,11 @@ public void setUniqueConstraintStrategy(UniqueConstraintSchemaUpdateStrategy uni @Override public void doMigration(Metadata metadata, ExecutionOptions options, TargetDescriptor targetDescriptor) { + SqlStringGenerationContext sqlStringGenerationContext = SqlStringGenerationContextImpl.fromConfigurationMap( + tool.getServiceRegistry().getService( JdbcEnvironment.class ), + metadata.getDatabase(), + options.getConfigurationValues() + ); if ( !targetDescriptor.getTargetTypes().isEmpty() ) { final JdbcContext jdbcContext = tool.resolveJdbcContext( options.getConfigurationValues() ); final DdlTransactionIsolator ddlTransactionIsolator = tool.getDdlTransactionIsolator( jdbcContext ); @@ -96,7 +104,7 @@ public void doMigration(Metadata metadata, ExecutionOptions options, TargetDescr final DatabaseInformation databaseInformation = Helper.buildDatabaseInformation( tool.getServiceRegistry(), ddlTransactionIsolator, - metadata.getDatabase().getDefaultNamespace().getName(), + sqlStringGenerationContext, tool ); @@ -112,7 +120,8 @@ public void doMigration(Metadata metadata, ExecutionOptions options, TargetDescr } try { - performMigration( metadata, databaseInformation, options, jdbcContext.getDialect(), targets ); + performMigration( metadata, databaseInformation, options, jdbcContext.getDialect(), + sqlStringGenerationContext, targets ); } finally { for ( GenerationTarget target : targets ) { @@ -150,13 +159,16 @@ protected abstract NameSpaceTablesInformation performTablesMigration( boolean tryToCreateCatalogs, boolean tryToCreateSchemas, Set exportedCatalogs, - Namespace namespace, GenerationTarget[] targets); + Namespace namespace, + SqlStringGenerationContext sqlStringGenerationContext, + GenerationTarget[] targets); private void performMigration( Metadata metadata, DatabaseInformation existingDatabase, ExecutionOptions options, Dialect dialect, + SqlStringGenerationContext sqlStringGenerationContext, GenerationTarget... targets) { final boolean format = Helper.interpretFormattingEnabled( options.getConfigurationValues() ); final Formatter formatter = format ? FormatStyle.DDL.getFormatter() : FormatStyle.NONE.getFormatter(); @@ -171,7 +183,7 @@ private void performMigration( applySqlStrings( true, dialect.getAuxiliaryDatabaseObjectExporter() - .getSqlDropStrings( auxiliaryDatabaseObject, metadata ), + .getSqlDropStrings( auxiliaryDatabaseObject, metadata, sqlStringGenerationContext ), formatter, options, targets @@ -184,7 +196,7 @@ private void performMigration( if ( !auxiliaryDatabaseObject.beforeTablesOnCreation() && auxiliaryDatabaseObject.appliesToDialect( dialect ) ) { applySqlStrings( true, - auxiliaryDatabaseObject.sqlCreateStrings( dialect ), + auxiliaryDatabaseObject.sqlCreateStrings( sqlStringGenerationContext ), formatter, options, targets @@ -216,7 +228,7 @@ private void performMigration( tryToCreateSchemas, exportedCatalogs, namespace, - targets + sqlStringGenerationContext, targets ); tablesInformation.put( namespace, nameSpaceTablesInformation ); if ( schemaFilter.includeNamespace( namespace ) ) { @@ -228,7 +240,8 @@ private void performMigration( false, dialect.getSequenceExporter().getSqlCreateStrings( sequence, - metadata + metadata, + sqlStringGenerationContext ), formatter, options, @@ -247,7 +260,8 @@ private void performMigration( if ( schemaFilter.includeTable( table ) ) { final TableInformation tableInformation = nameSpaceTablesInformation.getTableInformation( table ); if ( tableInformation == null || tableInformation.isPhysicalTable() ) { - applyForeignKeys( table, tableInformation, dialect, metadata, formatter, options, targets ); + applyForeignKeys( table, tableInformation, dialect, metadata, formatter, options, + sqlStringGenerationContext, targets ); } } } @@ -259,7 +273,7 @@ private void performMigration( if ( auxiliaryDatabaseObject.beforeTablesOnCreation() && auxiliaryDatabaseObject.appliesToDialect( dialect )) { applySqlStrings( true, - auxiliaryDatabaseObject.sqlCreateStrings( dialect ), + auxiliaryDatabaseObject.sqlCreateStrings( sqlStringGenerationContext ), formatter, options, targets @@ -274,10 +288,11 @@ protected void createTable( Metadata metadata, Formatter formatter, ExecutionOptions options, + SqlStringGenerationContext sqlStringGenerationContext, GenerationTarget... targets) { applySqlStrings( false, - dialect.getTableExporter().getSqlCreateStrings( table, metadata ), + dialect.getTableExporter().getSqlCreateStrings( table, metadata, sqlStringGenerationContext ), formatter, options, targets @@ -291,6 +306,7 @@ protected void migrateTable( Metadata metadata, Formatter formatter, ExecutionOptions options, + SqlStringGenerationContext sqlStringGenerationContext, GenerationTarget... targets) { final Database database = metadata.getDatabase(); @@ -301,8 +317,7 @@ protected void migrateTable( dialect, metadata, tableInformation, - database.getDefaultNamespace().getPhysicalName().getCatalog(), - database.getDefaultNamespace().getPhysicalName().getSchema() + sqlStringGenerationContext ), formatter, options, @@ -317,6 +332,7 @@ protected void applyIndexes( Metadata metadata, Formatter formatter, ExecutionOptions options, + SqlStringGenerationContext sqlStringGenerationContext, GenerationTarget... targets) { final Exporter exporter = dialect.getIndexExporter(); @@ -331,7 +347,7 @@ protected void applyIndexes( if ( existingIndex == null ) { applySqlStrings( false, - exporter.getSqlCreateStrings( index, metadata ), + exporter.getSqlCreateStrings( index, metadata, sqlStringGenerationContext ), formatter, options, targets @@ -352,6 +368,7 @@ protected void applyUniqueKeys( Metadata metadata, Formatter formatter, ExecutionOptions options, + SqlStringGenerationContext sqlStringGenerationContext, GenerationTarget... targets) { if ( uniqueConstraintStrategy == null ) { uniqueConstraintStrategy = determineUniqueConstraintSchemaUpdateStrategy( metadata ); @@ -374,7 +391,7 @@ protected void applyUniqueKeys( if ( uniqueConstraintStrategy == UniqueConstraintSchemaUpdateStrategy.DROP_RECREATE_QUIETLY ) { applySqlStrings( true, - exporter.getSqlDropStrings( uniqueKey, metadata ), + exporter.getSqlDropStrings( uniqueKey, metadata, sqlStringGenerationContext ), formatter, options, targets @@ -383,7 +400,7 @@ protected void applyUniqueKeys( applySqlStrings( true, - exporter.getSqlCreateStrings( uniqueKey, metadata ), + exporter.getSqlCreateStrings( uniqueKey, metadata, sqlStringGenerationContext ), formatter, options, targets @@ -410,6 +427,7 @@ protected void applyForeignKeys( Metadata metadata, Formatter formatter, ExecutionOptions options, + SqlStringGenerationContext sqlStringGenerationContext, GenerationTarget... targets) { if ( dialect.hasAlterTable() ) { final Exporter exporter = dialect.getForeignKeyExporter(); @@ -433,7 +451,7 @@ protected void applyForeignKeys( // in old SchemaUpdate code, this was the trigger to "create" applySqlStrings( false, - exporter.getSqlCreateStrings( foreignKey, metadata ), + exporter.getSqlCreateStrings( foreignKey, metadata, sqlStringGenerationContext ), formatter, options, targets @@ -517,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 ) ), @@ -536,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/AbstractSchemaValidator.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/AbstractSchemaValidator.java index ffe60ab61686..4b86bf903955 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/AbstractSchemaValidator.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/AbstractSchemaValidator.java @@ -13,7 +13,10 @@ import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.relational.Namespace; import org.hibernate.boot.model.relational.Sequence; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; +import org.hibernate.boot.model.relational.internal.SqlStringGenerationContextImpl; import org.hibernate.dialect.Dialect; +import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.mapping.Column; import org.hibernate.mapping.Selectable; import org.hibernate.mapping.Table; @@ -54,13 +57,18 @@ public AbstractSchemaValidator( @Override public void doValidation(Metadata metadata, ExecutionOptions options) { + SqlStringGenerationContext sqlStringGenerationContext = SqlStringGenerationContextImpl.fromConfigurationMap( + tool.getServiceRegistry().getService( JdbcEnvironment.class ), + metadata.getDatabase(), + options.getConfigurationValues() + ); final JdbcContext jdbcContext = tool.resolveJdbcContext( options.getConfigurationValues() ); final DdlTransactionIsolator isolator = tool.getDdlTransactionIsolator( jdbcContext ); final DatabaseInformation databaseInformation = Helper.buildDatabaseInformation( tool.getServiceRegistry(), isolator, - metadata.getDatabase().getDefaultNamespace().getName(), + sqlStringGenerationContext, tool ); 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 aada23b2aac0..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 @@ -11,6 +11,7 @@ import org.hibernate.boot.Metadata; import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.relational.Namespace; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.dialect.Dialect; import org.hibernate.engine.jdbc.internal.Formatter; import org.hibernate.mapping.Table; @@ -46,7 +47,9 @@ protected NameSpaceTablesInformation performTablesMigration( boolean tryToCreateCatalogs, boolean tryToCreateSchemas, Set exportedCatalogs, - Namespace namespace, GenerationTarget[] targets) { + Namespace namespace, + SqlStringGenerationContext sqlStringGenerationContext, + GenerationTarget[] targets) { final NameSpaceTablesInformation tablesInformation = new NameSpaceTablesInformation( metadata.getDatabase().getJdbcEnvironment().getIdentifierHelper() ); @@ -60,6 +63,7 @@ protected NameSpaceTablesInformation performTablesMigration( tryToCreateSchemas, exportedCatalogs, namespace, + sqlStringGenerationContext, targets ); final NameSpaceTablesInformation tables = existingDatabase.getTablesInformation( namespace ); @@ -68,11 +72,12 @@ protected NameSpaceTablesInformation performTablesMigration( checkExportIdentifier( table, exportIdentifiers ); final TableInformation tableInformation = tables.getTableInformation( table ); if ( tableInformation == null ) { - createTable( table, dialect, metadata, formatter, options, targets ); + createTable( table, dialect, metadata, formatter, options, sqlStringGenerationContext, targets ); } else if ( tableInformation.isPhysicalTable() ) { tablesInformation.addTableInformation( tableInformation ); - migrateTable( table, tableInformation, dialect, metadata, formatter, options, targets ); + migrateTable( table, tableInformation, dialect, metadata, formatter, options, + sqlStringGenerationContext, targets ); } } } @@ -81,8 +86,10 @@ else if ( tableInformation.isPhysicalTable() ) { if ( schemaFilter.includeTable( table ) && table.isPhysicalTable() ) { final TableInformation tableInformation = tablesInformation.getTableInformation( table ); if ( tableInformation == null || tableInformation.isPhysicalTable() ) { - applyIndexes( table, tableInformation, dialect, metadata, formatter, options, targets ); - applyUniqueKeys( table, tableInformation, dialect, metadata, formatter, options, targets ); + applyIndexes( table, tableInformation, dialect, metadata, formatter, options, + sqlStringGenerationContext, targets ); + applyUniqueKeys( table, tableInformation, dialect, metadata, formatter, options, + sqlStringGenerationContext, targets ); } } } diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/Helper.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/Helper.java index 131a961a074a..b09c9993586a 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/Helper.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/Helper.java @@ -14,7 +14,7 @@ import java.util.Map; import java.util.regex.Pattern; -import org.hibernate.boot.model.relational.Namespace; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.cfg.AvailableSettings; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; @@ -176,15 +176,15 @@ public static boolean interpretFormattingEnabled(Map configurationValues) { public static DatabaseInformation buildDatabaseInformation( ServiceRegistry serviceRegistry, DdlTransactionIsolator ddlTransactionIsolator, - Namespace.Name defaultNamespace, + SqlStringGenerationContext sqlStringGenerationContext, SchemaManagementTool tool) { final JdbcEnvironment jdbcEnvironment = serviceRegistry.getService( JdbcEnvironment.class ); try { return new DatabaseInformationImpl( serviceRegistry, jdbcEnvironment, + sqlStringGenerationContext, ddlTransactionIsolator, - defaultNamespace, tool ); } diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/HibernateSchemaManagementTool.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/HibernateSchemaManagementTool.java index 7741de5d8f5f..e6d5b97f0fee 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/HibernateSchemaManagementTool.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/HibernateSchemaManagementTool.java @@ -10,6 +10,7 @@ import java.util.Map; import org.hibernate.boot.model.naming.Identifier; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.boot.registry.selector.spi.StrategySelector; import org.hibernate.cfg.AvailableSettings; import org.hibernate.dialect.Dialect; @@ -375,16 +376,14 @@ private HibernateExtractionTool() { public ExtractionContext createExtractionContext( ServiceRegistry serviceRegistry, JdbcEnvironment jdbcEnvironment, + SqlStringGenerationContext sqlStringGenerationContext, DdlTransactionIsolator ddlTransactionIsolator, - Identifier defaultCatalog, - Identifier defaultSchema, ExtractionContext.DatabaseObjectAccess databaseObjectAccess) { return new ImprovedExtractionContextImpl( serviceRegistry, jdbcEnvironment, + sqlStringGenerationContext, ddlTransactionIsolator, - defaultCatalog, - defaultSchema, databaseObjectAccess ); } 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 0f2ae8a6dd0e..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 @@ -11,6 +11,7 @@ import org.hibernate.boot.Metadata; import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.relational.Namespace; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.dialect.Dialect; import org.hibernate.engine.jdbc.internal.Formatter; import org.hibernate.mapping.Table; @@ -47,6 +48,7 @@ protected NameSpaceTablesInformation performTablesMigration( boolean tryToCreateSchemas, Set exportedCatalogs, Namespace namespace, + SqlStringGenerationContext sqlStringGenerationContext, GenerationTarget[] targets) { final NameSpaceTablesInformation tablesInformation = new NameSpaceTablesInformation( metadata.getDatabase().getJdbcEnvironment().getIdentifierHelper() ); @@ -61,6 +63,7 @@ protected NameSpaceTablesInformation performTablesMigration( tryToCreateSchemas, exportedCatalogs, namespace, + sqlStringGenerationContext, targets ); for ( Table table : namespace.getTables() ) { @@ -68,11 +71,12 @@ protected NameSpaceTablesInformation performTablesMigration( checkExportIdentifier( table, exportIdentifiers ); final TableInformation tableInformation = existingDatabase.getTableInformation( table.getQualifiedTableName() ); if ( tableInformation == null ) { - createTable( table, dialect, metadata, formatter, options, targets ); + createTable( table, dialect, metadata, formatter, options, sqlStringGenerationContext, targets ); } else if ( tableInformation.isPhysicalTable() ) { tablesInformation.addTableInformation( tableInformation ); - migrateTable( table, tableInformation, dialect, metadata, formatter, options, targets ); + migrateTable( table, tableInformation, dialect, metadata, formatter, options, + sqlStringGenerationContext, targets ); } } } @@ -81,8 +85,10 @@ else if ( tableInformation.isPhysicalTable() ) { if ( schemaFilter.includeTable( table ) && table.isPhysicalTable() ) { final TableInformation tableInformation = tablesInformation.getTableInformation( table ); if ( tableInformation == null || tableInformation.isPhysicalTable() ) { - applyIndexes( table, tableInformation, dialect, metadata, formatter, options, targets ); - applyUniqueKeys( table, tableInformation, dialect, metadata, formatter, options, targets ); + applyIndexes( table, tableInformation, dialect, metadata, formatter, options, + sqlStringGenerationContext, targets ); + applyUniqueKeys( table, tableInformation, dialect, metadata, formatter, options, + sqlStringGenerationContext, targets ); } } } 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 f2d8e530a60e..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 @@ -23,6 +23,8 @@ import org.hibernate.boot.model.relational.InitCommand; import org.hibernate.boot.model.relational.Namespace; import org.hibernate.boot.model.relational.Sequence; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; +import org.hibernate.boot.model.relational.internal.SqlStringGenerationContextImpl; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.cfg.AvailableSettings; @@ -216,6 +218,9 @@ public void createFromMetadata( } final Database database = metadata.getDatabase(); + final JdbcEnvironment jdbcEnvironment = database.getJdbcEnvironment(); + SqlStringGenerationContext sqlStringGenerationContext = SqlStringGenerationContextImpl.fromConfigurationMap( + jdbcEnvironment, database, options.getConfigurationValues() ); final Set exportIdentifiers = new HashSet( 50 ); @@ -228,10 +233,12 @@ public void createFromMetadata( continue; } - 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 = sqlStringGenerationContext.catalogWithDefault( physicalName.getCatalog() ); if ( catalogPhysicalName != null && !exportedCatalogs.contains( catalogLogicalName ) ) { applySqlStrings( dialect.getCreateCatalogCommand( catalogPhysicalName.render( dialect ) ), @@ -243,13 +250,16 @@ public void createFromMetadata( } } - if ( tryToCreateSchemas && namespace.getPhysicalName().getSchema() != null ) { - applySqlStrings( - dialect.getCreateSchemaCommand( namespace.getPhysicalName().getSchema().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 + ); + } } } } @@ -265,7 +275,8 @@ public void createFromMetadata( applySqlStrings( dialect.getAuxiliaryDatabaseObjectExporter().getSqlCreateStrings( auxiliaryDatabaseObject, - metadata + metadata, + sqlStringGenerationContext ), formatter, options, @@ -290,7 +301,8 @@ public void createFromMetadata( applySqlStrings( dialect.getSequenceExporter().getSqlCreateStrings( sequence, - metadata + metadata, + sqlStringGenerationContext ), // dialect.getCreateSequenceStrings( // jdbcEnvironment.getQualifiedObjectNameFormatter().format( sequence.getName(), dialect ), @@ -313,7 +325,7 @@ public void createFromMetadata( } checkExportIdentifier( table, exportIdentifiers ); applySqlStrings( - dialect.getTableExporter().getSqlCreateStrings( table, metadata ), + dialect.getTableExporter().getSqlCreateStrings( table, metadata, sqlStringGenerationContext ), formatter, options, targets @@ -334,7 +346,9 @@ public void createFromMetadata( final Index index = (Index) indexItr.next(); checkExportIdentifier( index, exportIdentifiers ); applySqlStrings( - dialect.getIndexExporter().getSqlCreateStrings( index, metadata ), + dialect.getIndexExporter().getSqlCreateStrings( index, metadata, + sqlStringGenerationContext + ), formatter, options, targets @@ -347,7 +361,9 @@ public void createFromMetadata( final UniqueKey uniqueKey = (UniqueKey) ukItr.next(); checkExportIdentifier( uniqueKey, exportIdentifiers ); applySqlStrings( - dialect.getUniqueKeyExporter().getSqlCreateStrings( uniqueKey, metadata ), + dialect.getUniqueKeyExporter().getSqlCreateStrings( uniqueKey, metadata, + sqlStringGenerationContext + ), formatter, options, targets @@ -373,7 +389,9 @@ public void createFromMetadata( while ( fkItr.hasNext() ) { final ForeignKey foreignKey = (ForeignKey) fkItr.next(); applySqlStrings( - dialect.getForeignKeyExporter().getSqlCreateStrings( foreignKey, metadata ), + dialect.getForeignKeyExporter().getSqlCreateStrings( foreignKey, metadata, + sqlStringGenerationContext + ), formatter, options, targets @@ -388,7 +406,9 @@ public void createFromMetadata( && !auxiliaryDatabaseObject.beforeTablesOnCreation() ) { checkExportIdentifier( auxiliaryDatabaseObject, exportIdentifiers ); applySqlStrings( - dialect.getAuxiliaryDatabaseObjectExporter().getSqlCreateStrings( auxiliaryDatabaseObject, metadata ), + dialect.getAuxiliaryDatabaseObjectExporter().getSqlCreateStrings( auxiliaryDatabaseObject, metadata, + sqlStringGenerationContext + ), 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 5ea8918308fa..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 @@ -22,6 +22,8 @@ import org.hibernate.boot.model.relational.Exportable; import org.hibernate.boot.model.relational.Namespace; import org.hibernate.boot.model.relational.Sequence; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; +import org.hibernate.boot.model.relational.internal.SqlStringGenerationContextImpl; import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.dialect.Dialect; import org.hibernate.engine.config.spi.ConfigurationService; @@ -187,7 +189,8 @@ private void dropFromMetadata( Formatter formatter, GenerationTarget... targets) { final Database database = metadata.getDatabase(); - final JdbcEnvironment jdbcEnvironment = database.getJdbcEnvironment(); + SqlStringGenerationContext sqlStringGenerationContext = SqlStringGenerationContextImpl.fromConfigurationMap( + metadata.getDatabase().getJdbcEnvironment(), database, options.getConfigurationValues()); boolean tryToDropCatalogs = false; boolean tryToDropSchemas = false; @@ -213,7 +216,9 @@ private void dropFromMetadata( } applySqlStrings( - dialect.getAuxiliaryDatabaseObjectExporter().getSqlDropStrings( auxiliaryDatabaseObject, metadata ), + dialect.getAuxiliaryDatabaseObjectExporter().getSqlDropStrings( auxiliaryDatabaseObject, metadata, + sqlStringGenerationContext + ), formatter, options, targets @@ -227,7 +232,7 @@ private void dropFromMetadata( } // we need to drop all constraints/indexes prior to dropping the tables - applyConstraintDropping( namespace, metadata, formatter, options, targets ); + applyConstraintDropping( namespace, metadata, formatter, options, sqlStringGenerationContext, targets ); // now it's safe to drop the tables for ( Table table : namespace.getTables() ) { @@ -238,7 +243,9 @@ private void dropFromMetadata( continue; } checkExportIdentifier( table, exportIdentifiers ); - applySqlStrings( dialect.getTableExporter().getSqlDropStrings( table, metadata ), formatter, options,targets ); + applySqlStrings( dialect.getTableExporter().getSqlDropStrings( table, metadata, + sqlStringGenerationContext + ), formatter, options,targets ); } for ( Sequence sequence : namespace.getSequences() ) { @@ -246,7 +253,9 @@ private void dropFromMetadata( continue; } checkExportIdentifier( sequence, exportIdentifiers ); - applySqlStrings( dialect.getSequenceExporter().getSqlDropStrings( sequence, metadata ), formatter, options, targets ); + applySqlStrings( dialect.getSequenceExporter().getSqlDropStrings( sequence, metadata, + sqlStringGenerationContext + ), formatter, options, targets ); } } @@ -259,36 +268,41 @@ private void dropFromMetadata( } applySqlStrings( - auxiliaryDatabaseObject.sqlDropStrings( jdbcEnvironment.getDialect() ), + auxiliaryDatabaseObject.sqlDropStrings( sqlStringGenerationContext ), formatter, options, targets ); } - 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( @@ -310,6 +324,7 @@ private void applyConstraintDropping( Metadata metadata, Formatter formatter, ExecutionOptions options, + SqlStringGenerationContext sqlStringGenerationContext, GenerationTarget... targets) { final Dialect dialect = metadata.getDatabase().getJdbcEnvironment().getDialect(); @@ -329,7 +344,9 @@ private void applyConstraintDropping( while ( fks.hasNext() ) { final ForeignKey foreignKey = (ForeignKey) fks.next(); applySqlStrings( - dialect.getForeignKeyExporter().getSqlDropStrings( foreignKey, metadata ), + dialect.getForeignKeyExporter().getSqlDropStrings( foreignKey, metadata, + sqlStringGenerationContext + ), formatter, options, targets diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/StandardAuxiliaryDatabaseObjectExporter.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/StandardAuxiliaryDatabaseObjectExporter.java index abd43db6eaf4..773807908b44 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/StandardAuxiliaryDatabaseObjectExporter.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/StandardAuxiliaryDatabaseObjectExporter.java @@ -8,6 +8,7 @@ import org.hibernate.boot.Metadata; import org.hibernate.boot.model.relational.AuxiliaryDatabaseObject; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.dialect.Dialect; import org.hibernate.tool.schema.spi.Exporter; @@ -22,12 +23,14 @@ public StandardAuxiliaryDatabaseObjectExporter(Dialect dialect) { } @Override - public String[] getSqlCreateStrings(AuxiliaryDatabaseObject object, Metadata metadata) { - return object.sqlCreateStrings( dialect ); + public String[] getSqlCreateStrings(AuxiliaryDatabaseObject object, Metadata metadata, + SqlStringGenerationContext context) { + return object.sqlCreateStrings( context ); } @Override - public String[] getSqlDropStrings(AuxiliaryDatabaseObject object, Metadata metadata) { - return object.sqlDropStrings( dialect ); + public String[] getSqlDropStrings(AuxiliaryDatabaseObject object, Metadata metadata, + SqlStringGenerationContext context) { + return object.sqlDropStrings( context ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/StandardForeignKeyExporter.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/StandardForeignKeyExporter.java index b6a61e7dc41d..5668e9b56a80 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/StandardForeignKeyExporter.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/StandardForeignKeyExporter.java @@ -11,6 +11,7 @@ import org.hibernate.AssertionFailure; import org.hibernate.boot.Metadata; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.dialect.Dialect; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.mapping.Column; @@ -31,7 +32,7 @@ public StandardForeignKeyExporter(Dialect dialect) { } @Override - public String[] getSqlCreateStrings(ForeignKey foreignKey, Metadata metadata) { + public String[] getSqlCreateStrings(ForeignKey foreignKey, Metadata metadata, SqlStringGenerationContext context) { if ( !dialect.hasAlterTable() ) { return NO_COMMANDS; } @@ -90,15 +91,8 @@ public String[] getSqlCreateStrings(ForeignKey foreignKey, Metadata metadata) { i++; } - final JdbcEnvironment jdbcEnvironment = metadata.getDatabase().getJdbcEnvironment(); - final String sourceTableName = jdbcEnvironment.getQualifiedObjectNameFormatter().format( - foreignKey.getTable().getQualifiedTableName(), - dialect - ); - final String targetTableName = jdbcEnvironment.getQualifiedObjectNameFormatter().format( - foreignKey.getReferencedTable().getQualifiedTableName(), - dialect - ); + final String sourceTableName = context.format( foreignKey.getTable().getQualifiedTableName() ); + final String targetTableName = context.format( foreignKey.getReferencedTable().getQualifiedTableName() ); final StringBuilder buffer = new StringBuilder( dialect.getAlterTableString( sourceTableName ) ) .append( @@ -126,7 +120,7 @@ public String[] getSqlCreateStrings(ForeignKey foreignKey, Metadata metadata) { } @Override - public String[] getSqlDropStrings(ForeignKey foreignKey, Metadata metadata) { + public String[] getSqlDropStrings(ForeignKey foreignKey, Metadata metadata, SqlStringGenerationContext context) { if ( !dialect.hasAlterTable() ) { return NO_COMMANDS; } @@ -139,11 +133,7 @@ public String[] getSqlDropStrings(ForeignKey foreignKey, Metadata metadata) { return NO_COMMANDS; } - final JdbcEnvironment jdbcEnvironment = metadata.getDatabase().getJdbcEnvironment(); - final String sourceTableName = jdbcEnvironment.getQualifiedObjectNameFormatter().format( - foreignKey.getTable().getQualifiedTableName(), - dialect - ); + final String sourceTableName = context.format( foreignKey.getTable().getQualifiedTableName() ); return new String[] { getSqlDropStrings( sourceTableName, foreignKey, dialect ) }; diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/StandardIndexExporter.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/StandardIndexExporter.java index 426e6e1163a3..66def26486a6 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/StandardIndexExporter.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/StandardIndexExporter.java @@ -11,6 +11,7 @@ import org.hibernate.boot.Metadata; import org.hibernate.boot.model.relational.QualifiedNameImpl; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.dialect.Dialect; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.internal.util.StringHelper; @@ -29,22 +30,18 @@ public StandardIndexExporter(Dialect dialect) { } @Override - public String[] getSqlCreateStrings(Index index, Metadata metadata) { + public String[] getSqlCreateStrings(Index index, Metadata metadata, SqlStringGenerationContext context) { final JdbcEnvironment jdbcEnvironment = metadata.getDatabase().getJdbcEnvironment(); - final String tableName = jdbcEnvironment.getQualifiedObjectNameFormatter().format( - index.getTable().getQualifiedTableName(), - dialect - ); + final String tableName = context.format( index.getTable().getQualifiedTableName() ); final String indexNameForCreation; if ( dialect.qualifyIndexName() ) { - indexNameForCreation = jdbcEnvironment.getQualifiedObjectNameFormatter().format( + indexNameForCreation = context.format( new QualifiedNameImpl( index.getTable().getQualifiedTableName().getCatalogName(), index.getTable().getQualifiedTableName().getSchemaName(), jdbcEnvironment.getIdentifierHelper().toIdentifier( index.getQuotedName( dialect ) ) - ), - jdbcEnvironment.getDialect() + ) ); } else { @@ -78,16 +75,12 @@ public String[] getSqlCreateStrings(Index index, Metadata metadata) { } @Override - public String[] getSqlDropStrings(Index index, Metadata metadata) { + public String[] getSqlDropStrings(Index index, Metadata metadata, SqlStringGenerationContext context) { if ( !dialect.dropConstraints() ) { return NO_COMMANDS; } - final JdbcEnvironment jdbcEnvironment = metadata.getDatabase().getJdbcEnvironment(); - final String tableName = jdbcEnvironment.getQualifiedObjectNameFormatter().format( - index.getTable().getQualifiedTableName(), - dialect - ); + final String tableName = context.format( index.getTable().getQualifiedTableName() ); final String indexNameForCreation; if ( dialect.qualifyIndexName() ) { diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/StandardSequenceExporter.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/StandardSequenceExporter.java index cb04d29b5514..97dbe6b3dfc1 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/StandardSequenceExporter.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/StandardSequenceExporter.java @@ -9,8 +9,8 @@ import org.hibernate.boot.Metadata; import org.hibernate.boot.model.relational.QualifiedSequenceName; import org.hibernate.boot.model.relational.Sequence; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.dialect.Dialect; -import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.tool.schema.spi.Exporter; /** @@ -24,26 +24,23 @@ public StandardSequenceExporter(Dialect dialect) { } @Override - public String[] getSqlCreateStrings(Sequence sequence, Metadata metadata) { + public String[] getSqlCreateStrings(Sequence sequence, Metadata metadata, SqlStringGenerationContext context) { return dialect.getCreateSequenceStrings( - getFormattedSequenceName( sequence.getName(), metadata ), + getFormattedSequenceName( sequence.getName(), metadata, context ), sequence.getInitialValue(), sequence.getIncrementSize() ); } @Override - public String[] getSqlDropStrings(Sequence sequence, Metadata metadata) { + public String[] getSqlDropStrings(Sequence sequence, Metadata metadata, SqlStringGenerationContext context) { return dialect.getDropSequenceStrings( - getFormattedSequenceName( sequence.getName(), metadata ) + getFormattedSequenceName( sequence.getName(), metadata, context ) ); } - protected String getFormattedSequenceName(QualifiedSequenceName name, Metadata metadata) { - final JdbcEnvironment jdbcEnvironment = metadata.getDatabase().getJdbcEnvironment(); - return jdbcEnvironment.getQualifiedObjectNameFormatter().format( - name, - jdbcEnvironment.getDialect() - ); + protected String getFormattedSequenceName(QualifiedSequenceName name, Metadata metadata, + SqlStringGenerationContext context) { + return context.format( name ); } } 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 4b5534203ef4..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,8 +16,9 @@ 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.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.mapping.Column; import org.hibernate.mapping.Constraint; import org.hibernate.mapping.Table; @@ -35,23 +36,19 @@ public StandardTableExporter(Dialect dialect) { } @Override - public String[] getSqlCreateStrings(Table table, Metadata metadata) { + public String[] getSqlCreateStrings(Table table, Metadata metadata, + SqlStringGenerationContext context) { final QualifiedName tableName = new QualifiedNameParser.NameParts( Identifier.toIdentifier( table.getCatalog(), table.isCatalogQuoted() ), Identifier.toIdentifier( table.getSchema(), table.isSchemaQuoted() ), table.getNameIdentifier() ); - final JdbcEnvironment jdbcEnvironment = metadata.getDatabase().getJdbcEnvironment(); + String formattedTableName = context.format( tableName ); StringBuilder buf = new StringBuilder( tableCreateString( table.hasPrimaryKey() ) ) .append( ' ' ) - .append( - jdbcEnvironment.getQualifiedObjectNameFormatter().format( - tableName, - jdbcEnvironment.getDialect() - ) - ) + .append( formattedTableName ) .append( " (" ); @@ -115,7 +112,7 @@ public String[] getSqlCreateStrings(Table table, Metadata metadata) { uk.addColumn( col ); buf.append( dialect.getUniqueDelegate() - .getColumnDefinitionUniquenessFragment( col ) + .getColumnDefinitionUniquenessFragment( col, context ) ); } @@ -135,7 +132,7 @@ public String[] getSqlCreateStrings(Table table, Metadata metadata) { .append( table.getPrimaryKey().sqlConstraintString( dialect ) ); } - buf.append( dialect.getUniqueDelegate().getTableCreationUniqueConstraintsFragment( table ) ); + buf.append( dialect.getUniqueDelegate().getTableCreationUniqueConstraintsFragment( table, context ) ); applyTableCheck( table, buf ); @@ -150,31 +147,48 @@ 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 ); + 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 + "'" ); } } } } - protected void applyInitCommands(Table table, List sqlStrings) { - for ( InitCommand initCommand : table.getInitCommands() ) { + protected void applyInitCommands(Table table, List sqlStrings, SqlStringGenerationContext context) { + for ( InitCommand initCommand : table.getInitCommands( context ) ) { Collections.addAll( sqlStrings, initCommand.getInitCommands() ); } } @@ -200,7 +214,7 @@ protected String tableCreateString(boolean hasPrimaryKey) { } @Override - public String[] getSqlDropStrings(Table table, Metadata metadata) { + public String[] getSqlDropStrings(Table table, Metadata metadata, SqlStringGenerationContext context) { StringBuilder buf = new StringBuilder( "drop table " ); if ( dialect.supportsIfExistsBeforeTableName() ) { buf.append( "if exists " ); @@ -211,8 +225,7 @@ public String[] getSqlDropStrings(Table table, Metadata metadata) { Identifier.toIdentifier( table.getSchema(), table.isSchemaQuoted() ), table.getNameIdentifier() ); - final JdbcEnvironment jdbcEnvironment = metadata.getDatabase().getJdbcEnvironment(); - buf.append( jdbcEnvironment.getQualifiedObjectNameFormatter().format( tableName, jdbcEnvironment.getDialect() ) ) + buf.append( context.format( tableName ) ) .append( dialect.getCascadeConstraintsString() ); if ( dialect.supportsIfExistsAfterTableName() ) { diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/StandardUniqueKeyExporter.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/StandardUniqueKeyExporter.java index 260f9d9e8971..cee8e41180cd 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/StandardUniqueKeyExporter.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/StandardUniqueKeyExporter.java @@ -7,6 +7,7 @@ package org.hibernate.tool.schema.internal; import org.hibernate.boot.Metadata; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.dialect.Dialect; import org.hibernate.mapping.Constraint; import org.hibernate.mapping.UniqueKey; @@ -26,21 +27,25 @@ public StandardUniqueKeyExporter(Dialect dialect) { } @Override - public String[] getSqlCreateStrings(Constraint constraint, Metadata metadata) { + public String[] getSqlCreateStrings(Constraint constraint, Metadata metadata, + SqlStringGenerationContext context) { return new String[] { dialect.getUniqueDelegate().getAlterTableToAddUniqueKeyCommand( (UniqueKey) constraint, - metadata + metadata, + context ) }; } @Override - public String[] getSqlDropStrings(Constraint constraint, Metadata metadata) { + public String[] getSqlDropStrings(Constraint constraint, Metadata metadata, + SqlStringGenerationContext context) { return new String[] { dialect.getUniqueDelegate().getAlterTableToDropUniqueKeyCommand( (UniqueKey) constraint, - metadata + metadata, + context ) }; } diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/exec/ImprovedExtractionContextImpl.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/exec/ImprovedExtractionContextImpl.java index 926673d739f8..479419e0ab89 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/exec/ImprovedExtractionContextImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/exec/ImprovedExtractionContextImpl.java @@ -11,6 +11,7 @@ import java.sql.SQLException; import org.hibernate.boot.model.naming.Identifier; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.resource.transaction.spi.DdlTransactionIsolator; import org.hibernate.service.ServiceRegistry; @@ -22,9 +23,8 @@ public class ImprovedExtractionContextImpl implements ExtractionContext { private final ServiceRegistry serviceRegistry; private final JdbcEnvironment jdbcEnvironment; + private final SqlStringGenerationContext sqlStringGenerationContext; private final DdlTransactionIsolator ddlTransactionIsolator; - private final Identifier defaultCatalog; - private final Identifier defaultSchema; private final DatabaseObjectAccess databaseObjectAccess; @@ -33,15 +33,13 @@ public class ImprovedExtractionContextImpl implements ExtractionContext { public ImprovedExtractionContextImpl( ServiceRegistry serviceRegistry, JdbcEnvironment jdbcEnvironment, + SqlStringGenerationContext sqlStringGenerationContext, DdlTransactionIsolator ddlTransactionIsolator, - Identifier defaultCatalog, - Identifier defaultSchema, DatabaseObjectAccess databaseObjectAccess) { this.serviceRegistry = serviceRegistry; this.jdbcEnvironment = jdbcEnvironment; + this.sqlStringGenerationContext = sqlStringGenerationContext; this.ddlTransactionIsolator = ddlTransactionIsolator; - this.defaultCatalog = defaultCatalog; - this.defaultSchema = defaultSchema; this.databaseObjectAccess = databaseObjectAccess; } @@ -55,6 +53,11 @@ public JdbcEnvironment getJdbcEnvironment() { return jdbcEnvironment; } + @Override + public SqlStringGenerationContext getSqlStringGenerationContext() { + return sqlStringGenerationContext; + } + @Override public Connection getJdbcConnection() { return ddlTransactionIsolator.getIsolatedConnection(); @@ -78,12 +81,12 @@ public DatabaseMetaData getJdbcDatabaseMetaData() { @Override public Identifier getDefaultCatalog() { - return defaultCatalog; + return sqlStringGenerationContext.getDefaultCatalog(); } @Override public Identifier getDefaultSchema() { - return defaultSchema; + return sqlStringGenerationContext.getDefaultSchema(); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/spi/Exporter.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/spi/Exporter.java index 19e81d291371..016cb5223a21 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/spi/Exporter.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/spi/Exporter.java @@ -9,6 +9,7 @@ import org.hibernate.boot.Metadata; import org.hibernate.boot.model.relational.Exportable; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; /** * Defines a contract for exporting of database objects (tables, sequences, etc) for use in SQL {@code CREATE} and @@ -25,13 +26,41 @@ public interface Exporter { * Get the commands needed for creation. * * @return The commands needed for creation scripting. + * @deprecated Will be removed in favor of the variant accepting {@link SqlStringGenerationContext} + * @see #getSqlCreateStrings(Exportable, Metadata, SqlStringGenerationContext) */ - String[] getSqlCreateStrings(T exportable, Metadata metadata); + @Deprecated + default String[] getSqlCreateStrings(T exportable, Metadata metadata) { + throw new IllegalStateException("getSqlCreateStrings() was not implemented!"); + } + + /** + * Get the commands needed for creation. + * + * @return The commands needed for creation scripting. + */ + default String[] getSqlCreateStrings(T exportable, Metadata metadata, SqlStringGenerationContext context) { + return getSqlCreateStrings( exportable, metadata ); + } + + /** + * Get the commands needed for dropping. + * + * @return The commands needed for drop scripting. + * @deprecated Will be removed in favor of the variant accepting {@link SqlStringGenerationContext} + * @see #getSqlDropStrings(Exportable, Metadata, SqlStringGenerationContext) + */ + @Deprecated + default String[] getSqlDropStrings(T exportable, Metadata metadata) { + throw new IllegalStateException("getSqlDropStrings() was not implemented!"); + } /** * Get the commands needed for dropping. * * @return The commands needed for drop scripting. */ - String[] getSqlDropStrings(T exportable, Metadata metadata); + default String[] getSqlDropStrings(T exportable, Metadata metadata, SqlStringGenerationContext context) { + return getSqlDropStrings( exportable, metadata ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/spi/ExtractionTool.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/spi/ExtractionTool.java index e0b35cd58d4b..6a38c73620d2 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/spi/ExtractionTool.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/spi/ExtractionTool.java @@ -8,6 +8,7 @@ import org.hibernate.Incubating; import org.hibernate.boot.model.naming.Identifier; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.resource.transaction.spi.DdlTransactionIsolator; import org.hibernate.service.ServiceRegistry; @@ -26,9 +27,8 @@ public interface ExtractionTool { ExtractionContext createExtractionContext( ServiceRegistry serviceRegistry, JdbcEnvironment jdbcEnvironment, + SqlStringGenerationContext sqlStringGenerationContext, DdlTransactionIsolator ddlTransactionIsolator, - Identifier defaultCatalog, - Identifier defaultSchema, ExtractionContext.DatabaseObjectAccess databaseObjectAccess); InformationExtractor createInformationExtractor(ExtractionContext extractionContext); 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 29e5fe2e28c1..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,44 +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 { - - if ( oid == null ) { - if ( uniqueKeyPropertyName != null ) { - return resolve( session.getContextEntityIdentifier( owner ), session, owner ); - } - return null; - } - - //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/SequenceHiLoGeneratorNoIncrementTest.java b/hibernate-core/src/test/java/org/hibernate/id/SequenceHiLoGeneratorNoIncrementTest.java index 433cef1cb090..fca2b880956a 100644 --- a/hibernate-core/src/test/java/org/hibernate/id/SequenceHiLoGeneratorNoIncrementTest.java +++ b/hibernate-core/src/test/java/org/hibernate/id/SequenceHiLoGeneratorNoIncrementTest.java @@ -76,6 +76,7 @@ public void setUp() throws Exception { generator.registerExportables( metadata.getDatabase() ); sessionFactory = (SessionFactoryImplementor) metadata.buildSessionFactory(); + generator.initialize( sessionFactory.getSqlStringGenerationContext() ); sequenceValueExtractor = new SequenceValueExtractor( sessionFactory.getDialect(), TEST_SEQUENCE ); } diff --git a/hibernate-core/src/test/java/org/hibernate/id/SequenceHiLoGeneratorTest.java b/hibernate-core/src/test/java/org/hibernate/id/SequenceHiLoGeneratorTest.java index 6f8532764270..ce65ba2dea8a 100644 --- a/hibernate-core/src/test/java/org/hibernate/id/SequenceHiLoGeneratorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/id/SequenceHiLoGeneratorTest.java @@ -71,6 +71,7 @@ public void setUp() throws Exception { generator.registerExportables( metadata.getDatabase() ); sessionFactory = (SessionFactoryImplementor) metadata.buildSessionFactory(); + generator.initialize( sessionFactory.getSqlStringGenerationContext() ); sequenceValueExtractor = new SequenceValueExtractor( sessionFactory.getDialect(), TEST_SEQUENCE ); } diff --git a/hibernate-core/src/test/java/org/hibernate/id/SequenceStyleGeneratorBehavesLikeSequeceHiloGeneratorWitZeroIncrementSizeTest.java b/hibernate-core/src/test/java/org/hibernate/id/SequenceStyleGeneratorBehavesLikeSequeceHiloGeneratorWitZeroIncrementSizeTest.java index a818fa483a7e..f1c627e1af56 100644 --- a/hibernate-core/src/test/java/org/hibernate/id/SequenceStyleGeneratorBehavesLikeSequeceHiloGeneratorWitZeroIncrementSizeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/id/SequenceStyleGeneratorBehavesLikeSequeceHiloGeneratorWitZeroIncrementSizeTest.java @@ -74,6 +74,7 @@ protected MetadataBuildingContext getBuildingContext() { generator.registerExportables( metadata.getDatabase() ); sessionFactory = (SessionFactoryImplementor) metadata.buildSessionFactory(); + generator.initialize( sessionFactory.getSqlStringGenerationContext() ); sequenceValueExtractor = new SequenceValueExtractor( sessionFactory.getDialect(), TEST_SEQUENCE ); } 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/id/enhanced/SequenceStyleConfigUnitTest.java b/hibernate-core/src/test/java/org/hibernate/id/enhanced/SequenceStyleConfigUnitTest.java index c3eaf912a74f..4c00816b3eb2 100644 --- a/hibernate-core/src/test/java/org/hibernate/id/enhanced/SequenceStyleConfigUnitTest.java +++ b/hibernate-core/src/test/java/org/hibernate/id/enhanced/SequenceStyleConfigUnitTest.java @@ -11,6 +11,7 @@ import org.hibernate.MappingException; import org.hibernate.boot.internal.MetadataBuilderImpl; import org.hibernate.boot.model.relational.Database; +import org.hibernate.boot.model.relational.internal.SqlStringGenerationContextImpl; import org.hibernate.boot.registry.StandardServiceRegistry; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.cfg.AvailableSettings; @@ -45,13 +46,14 @@ public void testDefaultedSequenceBackedConfiguration() { SequenceStyleGenerator generator = new SequenceStyleGenerator(); generator.configure( StandardBasicTypes.LONG, props, serviceRegistry ); - generator.registerExportables( - new Database( new MetadataBuilderImpl.MetadataBuildingOptionsImpl( serviceRegistry ) ) - ); + Database database = new Database( new MetadataBuilderImpl.MetadataBuildingOptionsImpl( serviceRegistry ) ); + generator.registerExportables( database ); + generator.initialize( SqlStringGenerationContextImpl.forTests( database.getJdbcEnvironment() ) ); assertClassAssignability( SequenceStructure.class, generator.getDatabaseStructure().getClass() ); assertClassAssignability( NoopOptimizer.class, generator.getOptimizer().getClass() ); - assertEquals( SequenceStyleGenerator.DEF_SEQUENCE_NAME, generator.getDatabaseStructure().getName() ); + assertEquals( SequenceStyleGenerator.DEF_SEQUENCE_NAME, + generator.getDatabaseStructure().getPhysicalName().render() ); } } @@ -76,13 +78,14 @@ public void testDefaultedTableBackedConfiguration() { SequenceStyleGenerator generator = new SequenceStyleGenerator(); generator.configure( StandardBasicTypes.LONG, props, serviceRegistry ); - generator.registerExportables( - new Database( new MetadataBuilderImpl.MetadataBuildingOptionsImpl( serviceRegistry ) ) - ); + Database database = new Database( new MetadataBuilderImpl.MetadataBuildingOptionsImpl( serviceRegistry ) ); + generator.registerExportables( database ); + generator.initialize( SqlStringGenerationContextImpl.forTests( database.getJdbcEnvironment() ) ); assertClassAssignability( TableStructure.class, generator.getDatabaseStructure().getClass() ); assertClassAssignability( NoopOptimizer.class, generator.getOptimizer().getClass() ); - assertEquals( SequenceStyleGenerator.DEF_SEQUENCE_NAME, generator.getDatabaseStructure().getName() ); + assertEquals( SequenceStyleGenerator.DEF_SEQUENCE_NAME, + generator.getDatabaseStructure().getPhysicalName().render() ); } } @@ -103,13 +106,14 @@ public void testDefaultOptimizerBasedOnIncrementBackedBySequence() { SequenceStyleGenerator generator = new SequenceStyleGenerator(); generator.configure( StandardBasicTypes.LONG, props, serviceRegistry ); - generator.registerExportables( - new Database( new MetadataBuilderImpl.MetadataBuildingOptionsImpl( serviceRegistry ) ) - ); + Database database = new Database( new MetadataBuilderImpl.MetadataBuildingOptionsImpl( serviceRegistry ) ); + generator.registerExportables( database ); + generator.initialize( SqlStringGenerationContextImpl.forTests( database.getJdbcEnvironment() ) ); assertClassAssignability( TableStructure.class, generator.getDatabaseStructure().getClass() ); assertClassAssignability( PooledOptimizer.class, generator.getOptimizer().getClass() ); - assertEquals( SequenceStyleGenerator.DEF_SEQUENCE_NAME, generator.getDatabaseStructure().getName() ); + assertEquals( SequenceStyleGenerator.DEF_SEQUENCE_NAME, + generator.getDatabaseStructure().getPhysicalName().render() ); } // for dialects which do support pooled sequences, we default to pooled+sequence @@ -121,13 +125,14 @@ public void testDefaultOptimizerBasedOnIncrementBackedBySequence() { SequenceStyleGenerator generator = new SequenceStyleGenerator(); generator.configure( StandardBasicTypes.LONG, props, serviceRegistry ); - generator.registerExportables( - new Database( new MetadataBuilderImpl.MetadataBuildingOptionsImpl( serviceRegistry ) ) - ); + Database database = new Database( new MetadataBuilderImpl.MetadataBuildingOptionsImpl( serviceRegistry ) ); + generator.registerExportables( database ); + generator.initialize( SqlStringGenerationContextImpl.forTests( database.getJdbcEnvironment() ) ); assertClassAssignability( SequenceStructure.class, generator.getDatabaseStructure().getClass() ); assertClassAssignability( PooledOptimizer.class, generator.getOptimizer().getClass() ); - assertEquals( SequenceStyleGenerator.DEF_SEQUENCE_NAME, generator.getDatabaseStructure().getName() ); + assertEquals( SequenceStyleGenerator.DEF_SEQUENCE_NAME, + generator.getDatabaseStructure().getPhysicalName().render() ); } } @@ -146,13 +151,14 @@ public void testDefaultOptimizerBasedOnIncrementBackedByTable() { SequenceStyleGenerator generator = new SequenceStyleGenerator(); generator.configure( StandardBasicTypes.LONG, props, serviceRegistry ); - generator.registerExportables( - new Database( new MetadataBuilderImpl.MetadataBuildingOptionsImpl( serviceRegistry ) ) - ); + Database database = new Database( new MetadataBuilderImpl.MetadataBuildingOptionsImpl( serviceRegistry ) ); + generator.registerExportables( database ); + generator.initialize( SqlStringGenerationContextImpl.forTests( database.getJdbcEnvironment() ) ); assertClassAssignability( TableStructure.class, generator.getDatabaseStructure().getClass() ); assertClassAssignability( PooledOptimizer.class, generator.getOptimizer().getClass() ); - assertEquals( SequenceStyleGenerator.DEF_SEQUENCE_NAME, generator.getDatabaseStructure().getName() ); + assertEquals( SequenceStyleGenerator.DEF_SEQUENCE_NAME, + generator.getDatabaseStructure().getPhysicalName().render() ); } } @@ -169,13 +175,14 @@ public void testForceTableUse() { SequenceStyleGenerator generator = new SequenceStyleGenerator(); generator.configure( StandardBasicTypes.LONG, props, serviceRegistry ); - generator.registerExportables( - new Database( new MetadataBuilderImpl.MetadataBuildingOptionsImpl( serviceRegistry ) ) - ); + Database database = new Database( new MetadataBuilderImpl.MetadataBuildingOptionsImpl( serviceRegistry ) ); + generator.registerExportables( database ); + generator.initialize( SqlStringGenerationContextImpl.forTests( database.getJdbcEnvironment() ) ); assertClassAssignability( TableStructure.class, generator.getDatabaseStructure().getClass() ); assertClassAssignability( NoopOptimizer.class, generator.getOptimizer().getClass() ); - assertEquals( SequenceStyleGenerator.DEF_SEQUENCE_NAME, generator.getDatabaseStructure().getName() ); + assertEquals( SequenceStyleGenerator.DEF_SEQUENCE_NAME, + generator.getDatabaseStructure().getPhysicalName().render() ); } } @@ -193,9 +200,9 @@ public void testExplicitOptimizerWithExplicitIncrementSize() { props.setProperty( SequenceStyleGenerator.INCREMENT_PARAM, "20" ); SequenceStyleGenerator generator = new SequenceStyleGenerator(); generator.configure( StandardBasicTypes.LONG, props, serviceRegistry ); - generator.registerExportables( - new Database( new MetadataBuilderImpl.MetadataBuildingOptionsImpl( serviceRegistry ) ) - ); + Database database = new Database( new MetadataBuilderImpl.MetadataBuildingOptionsImpl( serviceRegistry ) ); + generator.registerExportables( database ); + generator.initialize( SqlStringGenerationContextImpl.forTests( database.getJdbcEnvironment() ) ); assertClassAssignability( SequenceStructure.class, generator.getDatabaseStructure().getClass() ); assertClassAssignability( NoopOptimizer.class, generator.getOptimizer().getClass() ); @@ -208,9 +215,8 @@ public void testExplicitOptimizerWithExplicitIncrementSize() { props.setProperty( SequenceStyleGenerator.INCREMENT_PARAM, "20" ); generator = new SequenceStyleGenerator(); generator.configure( StandardBasicTypes.LONG, props, serviceRegistry ); - generator.registerExportables( - new Database( new MetadataBuilderImpl.MetadataBuildingOptionsImpl( serviceRegistry ) ) - ); + generator.registerExportables( database ); + generator.initialize( SqlStringGenerationContextImpl.forTests( database.getJdbcEnvironment() ) ); assertClassAssignability( SequenceStructure.class, generator.getDatabaseStructure().getClass() ); assertClassAssignability( HiLoOptimizer.class, generator.getOptimizer().getClass() ); assertEquals( 20, generator.getOptimizer().getIncrementSize() ); @@ -222,9 +228,8 @@ public void testExplicitOptimizerWithExplicitIncrementSize() { props.setProperty( SequenceStyleGenerator.INCREMENT_PARAM, "20" ); generator = new SequenceStyleGenerator(); generator.configure( StandardBasicTypes.LONG, props, serviceRegistry ); - generator.registerExportables( - new Database( new MetadataBuilderImpl.MetadataBuildingOptionsImpl( serviceRegistry ) ) - ); + generator.registerExportables( database ); + generator.initialize( SqlStringGenerationContextImpl.forTests( database.getJdbcEnvironment() ) ); // because the dialect reports to not support pooled seqyences, the expectation is that we will // use a table for the backing structure... assertClassAssignability( TableStructure.class, generator.getDatabaseStructure().getClass() ); @@ -243,27 +248,25 @@ public void testPreferredPooledOptimizerSetting() { props.setProperty( SequenceStyleGenerator.INCREMENT_PARAM, "20" ); SequenceStyleGenerator generator = new SequenceStyleGenerator(); generator.configure( StandardBasicTypes.LONG, props, serviceRegistry ); - generator.registerExportables( - new Database( new MetadataBuilderImpl.MetadataBuildingOptionsImpl( serviceRegistry ) ) - ); + Database database = new Database( new MetadataBuilderImpl.MetadataBuildingOptionsImpl( serviceRegistry ) ); + generator.registerExportables( database ); + generator.initialize( SqlStringGenerationContextImpl.forTests( database.getJdbcEnvironment() ) ); assertClassAssignability( SequenceStructure.class, generator.getDatabaseStructure().getClass() ); assertClassAssignability( PooledOptimizer.class, generator.getOptimizer().getClass() ); props.setProperty( Environment.PREFER_POOLED_VALUES_LO, "true" ); generator = new SequenceStyleGenerator(); generator.configure( StandardBasicTypes.LONG, props, serviceRegistry ); - generator.registerExportables( - new Database( new MetadataBuilderImpl.MetadataBuildingOptionsImpl( serviceRegistry ) ) - ); + generator.registerExportables( database ); + generator.initialize( SqlStringGenerationContextImpl.forTests( database.getJdbcEnvironment() ) ); assertClassAssignability( SequenceStructure.class, generator.getDatabaseStructure().getClass() ); assertClassAssignability( PooledLoOptimizer.class, generator.getOptimizer().getClass() ); props.setProperty( Environment.PREFERRED_POOLED_OPTIMIZER, StandardOptimizerDescriptor.POOLED_LOTL.getExternalName() ); generator = new SequenceStyleGenerator(); generator.configure( StandardBasicTypes.LONG, props, serviceRegistry ); - generator.registerExportables( - new Database( new MetadataBuilderImpl.MetadataBuildingOptionsImpl( serviceRegistry ) ) - ); + generator.registerExportables( database ); + generator.initialize( SqlStringGenerationContextImpl.forTests( database.getJdbcEnvironment() ) ); assertClassAssignability( SequenceStructure.class, generator.getDatabaseStructure().getClass() ); assertClassAssignability( PooledLoThreadLocalOptimizer.class, generator.getOptimizer().getClass() ); } diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/beanvalidation/BeanValidationTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/beanvalidation/BeanValidationTest.java index 7e811e2787d9..4567981b56d0 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/beanvalidation/BeanValidationTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/beanvalidation/BeanValidationTest.java @@ -86,10 +86,10 @@ public void testBeanValidationIntegrationOnCommit() { @RequiresDialect(H2Dialect.class) public void testTitleColumnHasExpectedLength() { EntityManager em = getOrCreateEntityManager(); - int len = (Integer) em.createNativeQuery( + Number len = (Number) em.createNativeQuery( "select CHARACTER_MAXIMUM_LENGTH from INFORMATION_SCHEMA.COLUMNS c where c.TABLE_NAME = 'CUPHOLDER' and c.COLUMN_NAME = 'TITLE'" ).getSingleResult(); - assertEquals( 64, len ); + assertEquals( 64, len.intValue() ); } @Override diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/components/joins/ManyToOneType.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/components/joins/ManyToOneType.java index a902ad752cc3..91bb3b6d91b9 100755 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/components/joins/ManyToOneType.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/components/joins/ManyToOneType.java @@ -6,6 +6,7 @@ */ package org.hibernate.jpa.test.criteria.components.joins; +import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; @@ -18,6 +19,7 @@ public class ManyToOneType { @Id @GeneratedValue public Long id; + @Column(name = "val") private String value; public ManyToOneType() { 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 6537ad7b5e4a..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 @@ -24,6 +24,7 @@ package org.hibernate.jpa.test.criteria.selectcase; import java.util.List; +import javax.persistence.Column; import javax.persistence.EntityManager; import javax.persistence.EnumType; import javax.persistence.Enumerated; @@ -37,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; @@ -47,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 @@ -131,6 +134,7 @@ public static class Entity { private Long id; @Enumerated(EnumType.STRING) + @Column(name = "val") private EnumValue value; } diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/simplecase/BasicSimpleCaseTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/simplecase/BasicSimpleCaseTest.java index 111fc4aff102..a50826bd03e7 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/simplecase/BasicSimpleCaseTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/simplecase/BasicSimpleCaseTest.java @@ -13,6 +13,7 @@ import java.util.List; import javax.persistence.AttributeConverter; +import javax.persistence.Column; import javax.persistence.Convert; import javax.persistence.Entity; import javax.persistence.EntityManager; @@ -311,6 +312,7 @@ public static class TestEntity { @Convert(converter = TestEnumConverter.class) private TestEnum enumField; + @Column(name = "val") private Long value; public long getId() { 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/metamodel/Entity1.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/Entity1.java index 09d6be664b8c..76aba439399c 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/Entity1.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/Entity1.java @@ -23,6 +23,7 @@ */ package org.hibernate.jpa.test.metamodel; +import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.JoinColumn; @@ -39,5 +40,6 @@ public class Entity1 { @JoinColumn(name="entity2_id", nullable = false) private Entity2 entity2; + @Column(name = "val") private String value; } diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/Entity2.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/Entity2.java index 362ccc11eb81..417a98d4df88 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/Entity2.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/Entity2.java @@ -23,6 +23,7 @@ */ package org.hibernate.jpa.test.metamodel; +import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.JoinColumn; @@ -39,5 +40,6 @@ public class Entity2 { @JoinColumn(name="entity3_id") private Entity3 entity3; + @Column(name = "val") private String value; } diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/Entity3.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/Entity3.java index af804816b60f..7a19ded3c2e6 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/Entity3.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/Entity3.java @@ -23,6 +23,7 @@ */ package org.hibernate.jpa.test.metamodel; +import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; @@ -33,5 +34,6 @@ public class Entity3 { @Id private long id; + @Column(name = "val") private String value; } 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/schemagen/JpaSchemaGeneratorTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/schemagen/JpaSchemaGeneratorTest.java index 51aa15989987..d0fc4f47c192 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/schemagen/JpaSchemaGeneratorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/schemagen/JpaSchemaGeneratorTest.java @@ -154,7 +154,7 @@ private void doTest(Map settings) { // We want a fresh db after emf close // Unfortunately we have to use this dirty hack because the db seems not to be closed otherwise settings.put( "hibernate.connection.url", "jdbc:h2:mem:db-schemagen" + schemagenNumber++ - + ";MVCC=TRUE;LOCK_TIMEOUT=10000" ); + + ";LOCK_TIMEOUT=10000" ); EntityManagerFactoryBuilder emfb = Bootstrap.getEntityManagerFactoryBuilder( buildPersistenceUnitDescriptor(), settings ); EntityManagerFactory emf = emfb.build(); 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 0deba30d7dd6..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 @@ -18,6 +18,7 @@ import javax.persistence.spi.PersistenceUnitInfo; import javax.sql.DataSource; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.boot.spi.SessionFactoryOptions; import org.hibernate.cache.spi.access.AccessType; @@ -372,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(); @@ -519,7 +521,8 @@ public void prepare( JdbcServices jdbcServices, JdbcConnectionAccess connectionAccess, MetadataImplementor metadata, - SessionFactoryOptions sessionFactoryOptions) { + SessionFactoryOptions sessionFactoryOptions, + SqlStringGenerationContext sqlStringGenerationContext) { } 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/criteria/internal/hhh14197/Employee.java b/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/hhh14197/Employee.java index 20ef889104f6..681cf12ef65d 100644 --- a/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/hhh14197/Employee.java +++ b/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/hhh14197/Employee.java @@ -88,7 +88,7 @@ public void setDirectReports(Set directReports) { @ElementCollection @MapKeyColumn(name = "name", length = 180) - @Column(name = "value", nullable = false) + @Column(name = "val", nullable = false) public Map getAnnotations() { return this.annotations; } 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/any/IntegerProperty.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/any/IntegerProperty.java index f3407ea8a8fa..9c8a9ca586c0 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/any/IntegerProperty.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/any/IntegerProperty.java @@ -17,6 +17,7 @@ public class IntegerProperty implements Property { private Integer id; private String name; + @Column(name = "val") private Integer value; public IntegerProperty() { diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/any/StringProperty.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/any/StringProperty.java index 29b3bfbe5ee0..6d063435ce8b 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/any/StringProperty.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/any/StringProperty.java @@ -16,6 +16,7 @@ public class StringProperty implements Property { private Integer id; private String name; + @Column(name = "val") private String value; public StringProperty() { 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/id/generationmappings/NewGeneratorMappingsTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/id/generationmappings/NewGeneratorMappingsTest.java index 9a778187a522..06c65b110819 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/id/generationmappings/NewGeneratorMappingsTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/id/generationmappings/NewGeneratorMappingsTest.java @@ -66,7 +66,8 @@ public void testMinimalSequenceEntity() { IdentifierGenerator generator = persister.getIdentifierGenerator(); assertTrue( SequenceStyleGenerator.class.isInstance( generator ) ); SequenceStyleGenerator seqGenerator = (SequenceStyleGenerator) generator; - assertEquals( MinimalSequenceEntity.SEQ_NAME, seqGenerator.getDatabaseStructure().getName() ); + assertEquals( MinimalSequenceEntity.SEQ_NAME, + seqGenerator.getDatabaseStructure().getPhysicalName().render() ); // 1 is the annotation default assertEquals( 1, seqGenerator.getDatabaseStructure().getInitialValue() ); // 50 is the annotation default @@ -91,7 +92,8 @@ public void testAutoEntity() { IdentifierGenerator generator = persister.getIdentifierGenerator(); assertTrue( SequenceStyleGenerator.class.isInstance( generator ) ); SequenceStyleGenerator seqGenerator = (SequenceStyleGenerator) generator; - assertEquals( SequenceStyleGenerator.DEF_SEQUENCE_NAME, seqGenerator.getDatabaseStructure().getName() ); + assertEquals( SequenceStyleGenerator.DEF_SEQUENCE_NAME, + seqGenerator.getDatabaseStructure().getPhysicalName().render() ); assertEquals( SequenceStyleGenerator.DEFAULT_INITIAL_VALUE, seqGenerator.getDatabaseStructure().getInitialValue() ); assertEquals( SequenceStyleGenerator.DEFAULT_INCREMENT_SIZE, seqGenerator.getDatabaseStructure().getIncrementSize() ); } @@ -123,7 +125,7 @@ public void testSequencePerEntity() { SequenceStyleGenerator seqGenerator = (SequenceStyleGenerator) generator; assertEquals( StringHelper.unqualifyEntityName( DedicatedSequenceEntity1.class.getName() ) + DedicatedSequenceEntity1.SEQUENCE_SUFFIX, - seqGenerator.getDatabaseStructure().getName() + seqGenerator.getDatabaseStructure().getPhysicalName().render() ); // Checking second entity. @@ -133,7 +135,7 @@ public void testSequencePerEntity() { seqGenerator = (SequenceStyleGenerator) generator; assertEquals( DedicatedSequenceEntity2.ENTITY_NAME + DedicatedSequenceEntity1.SEQUENCE_SUFFIX, - seqGenerator.getDatabaseStructure().getName() + seqGenerator.getDatabaseStructure().getPhysicalName().render() ); } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/id/sequences/HibernateSequenceTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/id/sequences/HibernateSequenceTest.java index 2117b99b5f46..efd1b47d596e 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/id/sequences/HibernateSequenceTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/id/sequences/HibernateSequenceTest.java @@ -11,8 +11,8 @@ import org.hibernate.Session; import org.hibernate.Transaction; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.cfg.Configuration; -import org.hibernate.cfg.Environment; import org.hibernate.dialect.H2Dialect; import org.hibernate.id.IdentifierGenerator; import org.hibernate.id.enhanced.SequenceStyleGenerator; @@ -55,9 +55,10 @@ public void testHibernateSequenceSchema() { IdentifierGenerator generator = persister.getIdentifierGenerator(); Assert.assertTrue( SequenceStyleGenerator.class.isInstance( generator ) ); SequenceStyleGenerator seqGenerator = (SequenceStyleGenerator) generator; + SqlStringGenerationContext sqlStringGenerationContext = sessionFactory().getSqlStringGenerationContext(); Assert.assertEquals( - Table.qualify( null, SCHEMA_NAME, SequenceStyleGenerator.DEF_SEQUENCE_NAME ), - seqGenerator.getDatabaseStructure().getName() + SCHEMA_NAME + "." + SequenceStyleGenerator.DEF_SEQUENCE_NAME, + sqlStringGenerationContext.format( seqGenerator.getDatabaseStructure().getPhysicalName() ) ); } 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/SerialNumber.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/SerialNumber.java index feadec8119bc..405cb8e6286c 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/SerialNumber.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/SerialNumber.java @@ -18,6 +18,7 @@ @Entity public class SerialNumber { private SerialNumberPk id; + @Column(name = "val") private String value; public boolean equals(Object o) { 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/Attrvalue.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/query/Attrvalue.java index 5bf4e1ee8395..823896693073 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/query/Attrvalue.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/query/Attrvalue.java @@ -6,6 +6,7 @@ */ package org.hibernate.test.annotations.query; +import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; @@ -15,7 +16,7 @@ public class Attrvalue { @Id @GeneratedValue private Long id; - + @Column(name = "val") private String value; public Long getId() { 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/reflection/JPAXMLOverriddenAnnotationReaderTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/JPAXMLOverriddenAnnotationReaderTest.java index 1c12a7a146cd..dcf8d8c7d47b 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/JPAXMLOverriddenAnnotationReaderTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/JPAXMLOverriddenAnnotationReaderTest.java @@ -64,7 +64,9 @@ public void testEntityRelatedAnnotations() throws Exception { ); assertNotNull( reader.getAnnotation( Table.class ) ); assertEquals( "@Table not overridden", "tbl_admin", reader.getAnnotation( Table.class ).name() ); - assertEquals( "Default schema not overridden", "myschema", reader.getAnnotation( Table.class ).schema() ); + // The default schema is assigned later, when we generate SQL. + // See DefaultCatalogAndSchemaTest. + assertEquals( "Default schema overridden too soon", "", reader.getAnnotation( Table.class ).schema() ); assertEquals( "Proper @Table.uniqueConstraints", 2, reader.getAnnotation( Table.class ).uniqueConstraints()[0].columnNames().length @@ -88,7 +90,9 @@ public void testEntityRelatedAnnotations() throws Exception { assertEquals( "default fails", 50, reader.getAnnotation( SequenceGenerator.class ).allocationSize() ); assertNotNull( "TableOverriding not working", reader.getAnnotation( TableGenerator.class ) ); assertEquals( "wrong tble name", "tablehilo", reader.getAnnotation( TableGenerator.class ).table() ); - assertEquals( "no schema overriding", "myschema", reader.getAnnotation( TableGenerator.class ).schema() ); + // The default schema is assigned later, when we generate SQL. + // See DefaultCatalogAndSchemaTest. + assertEquals( "Default schema overridden too soon", "", reader.getAnnotation( TableGenerator.class ).schema() ); reader = new JPAXMLOverriddenAnnotationReader( Match.class, context, bootstrapContext ); assertNotNull( reader.getAnnotation( Table.class ) ); @@ -98,10 +102,14 @@ public void testEntityRelatedAnnotations() throws Exception { assertEquals( "Java annotation not taken into account", "matchschema", reader.getAnnotation( Table.class ).schema() ); - assertEquals( "Overriding not taken into account", "mycatalog", reader.getAnnotation( Table.class ).catalog() ); + // The default schema is assigned later, when we generate SQL. + // See DefaultCatalogAndSchemaTest. + assertEquals( "Default catalog overridden too soon", "", reader.getAnnotation( Table.class ).catalog() ); assertNotNull( "SecondaryTable swallowed", reader.getAnnotation( SecondaryTables.class ) ); + // The default schema is assigned later, when we generate SQL. + // See DefaultCatalogAndSchemaTest. assertEquals( - "Default schema not taken into account", "myschema", + "Default schema not taken into account", "", reader.getAnnotation( SecondaryTables.class ).value()[0].schema() ); assertNotNull( reader.getAnnotation( Inheritance.class ) ); @@ -194,15 +202,14 @@ public void testEntityRelatedAnnotationsMetadataComplete() throws Exception { assertEquals( "Metadata complete should ignore java annotations", "", reader.getAnnotation( Entity.class ).name() ); - assertNotNull( reader.getAnnotation( Table.class ) ); - assertEquals( "@Table should not be used", "", reader.getAnnotation( Table.class ).name() ); - assertEquals( "Default schema not overriden", "myschema", reader.getAnnotation( Table.class ).schema() ); + // The default schema is assigned later, when we generate SQL. + // See DefaultCatalogAndSchemaTest. + assertNull( "Default schema overridden too soon", reader.getAnnotation( Table.class ) ); reader = new JPAXMLOverriddenAnnotationReader( Match.class, context, bootstrapContext ); - assertNotNull( reader.getAnnotation( Table.class ) ); - assertEquals( "@Table should not be used", "", reader.getAnnotation( Table.class ).name() ); - assertEquals( "Overriding not taken into account", "myschema", reader.getAnnotation( Table.class ).schema() ); - assertEquals( "Overriding not taken into account", "mycatalog", reader.getAnnotation( Table.class ).catalog() ); + // The default schema is assigned later, when we generate SQL. + // See DefaultCatalogAndSchemaTest. + assertNull( "Default schema overridden too soon", reader.getAnnotation( Table.class ) ); assertNull( "Ignore Java annotation", reader.getAnnotation( SecondaryTable.class ) ); assertNull( "Ignore Java annotation", reader.getAnnotation( SecondaryTables.class ) ); assertNull( "Ignore Java annotation", reader.getAnnotation( Inheritance.class ) ); 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/booleans/BooleanMappingTest.java b/hibernate-core/src/test/java/org/hibernate/test/booleans/BooleanMappingTest.java new file mode 100644 index 000000000000..adf08a0f7efa --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/booleans/BooleanMappingTest.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 . + */ +package org.hibernate.test.booleans; + +import java.util.List; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * Useful to verify Boolean mappings; in particular we had a new + * error on recent versions of H2, so let's ensure this works + * on whatever version the testsuite is being run with. + */ +public class BooleanMappingTest extends BaseCoreFunctionalTestCase { + + @Test + @TestForIssue(jiraKey = "HHH-15002") + public void testFetchEager() { + doInHibernate( this::sessionFactory, s -> { + Event activeEvent = new Event(); + activeEvent.setActive( Boolean.TRUE ); + s.persist( activeEvent ); + + Event inactiveEvent = new Event(); + inactiveEvent.setActive( Boolean.FALSE ); + s.persist( inactiveEvent ); + } ); + + final List activeEvents = doInHibernate( this::sessionFactory, s -> { + return s.createQuery( "SELECT e FROM Event e WHERE e.active = true", Event.class ).getResultList(); + } ); + + assertNotNull( activeEvents ); + assertEquals( 1, activeEvents.size() ); + + final List allEvents = doInHibernate( this::sessionFactory, s -> { + return s.createQuery( "FROM Event", Event.class ).getResultList(); + } ); + + assertNotNull( allEvents ); + assertEquals( 2, allEvents.size() ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Event.class, + }; + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/booleans/Event.java b/hibernate-core/src/test/java/org/hibernate/test/booleans/Event.java new file mode 100644 index 000000000000..37503f3e287a --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/booleans/Event.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 . + */ +package org.hibernate.test.booleans; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +@Entity +public class Event { + + @Id + @GeneratedValue + private Long id; + + private Boolean active; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Boolean getActive() { + return active; + } + + public void setActive(Boolean active) { + this.active = active; + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/boot/database/qualifiedTableNaming/DefaultCatalogAndSchemaTest.java b/hibernate-core/src/test/java/org/hibernate/test/boot/database/qualifiedTableNaming/DefaultCatalogAndSchemaTest.java new file mode 100644 index 000000000000..a23b1ac04684 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/boot/database/qualifiedTableNaming/DefaultCatalogAndSchemaTest.java @@ -0,0 +1,1337 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 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.qualifiedTableNaming; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; +import javax.persistence.Basic; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.ForeignKey; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.OneToMany; +import javax.persistence.SequenceGenerator; +import javax.persistence.Table; +import javax.persistence.TableGenerator; + +import org.hibernate.MappingException; +import org.hibernate.annotations.GenericGenerator; +import org.hibernate.annotations.Parameter; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.SQLInsert; +import org.hibernate.annotations.SQLUpdate; +import org.hibernate.boot.MetadataBuilder; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.internal.MetadataImpl; +import org.hibernate.boot.internal.SessionFactoryBuilderImpl; +import org.hibernate.boot.internal.SessionFactoryOptionsBuilder; +import org.hibernate.boot.registry.BootstrapServiceRegistry; +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.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; +import org.hibernate.hql.spi.id.global.GlobalTemporaryTableBulkIdStrategy; +import org.hibernate.hql.spi.id.local.LocalTemporaryTableBulkIdStrategy; +import org.hibernate.id.IdentifierGenerator; +import org.hibernate.persister.entity.AbstractEntityPersister; +import org.hibernate.service.spi.ServiceRegistryImplementor; +import org.hibernate.tool.hbm2ddl.SchemaExport; +import org.hibernate.tool.schema.TargetType; +import org.hibernate.tool.schema.internal.exec.ScriptTargetOutputToWriter; +import org.hibernate.tool.schema.spi.DelayedDropRegistryNotAvailableImpl; +import org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator; +import org.hibernate.tool.schema.spi.ScriptTargetOutput; +import org.hibernate.tool.schema.spi.TargetDescriptor; + +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; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(CustomParameterized.class) +@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, + comment = "MariaDB < 10.3 doesn't support sequences") +@SkipForDialect(value = MariaDB102Dialect.class, strictMatching = true, + comment = "MariaDB < 10.3 doesn't support sequences") +public class DefaultCatalogAndSchemaTest { + + private static final String SQL_QUOTE_CHARACTER_CLASS = "([`\"]|\\[|\\])"; + + 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. + private static final String CUSTOM_INSERT_SQL_PART_1 = "insert into {h-catalog}{h-schema}"; + private static final String CUSTOM_INSERT_SQL_PART_2 = ", {h-domain}"; + private static final String CUSTOM_INSERT_SQL_PART_3 = " VALUES(basic = ?)"; + private static final String CUSTOM_UPDATE_SQL_PART_1 = "update {h-catalog}{h-schema}"; + private static final String CUSTOM_UPDATE_SQL_PART_2 = ", {h-domain}"; + private static final String CUSTOM_UPDATE_SQL_PART_3 = " SET basic = ?"; + private static final String CUSTOM_DELETE_SQL_PART_1 = "delete from {h-catalog}{h-schema}"; + private static final String CUSTOM_DELETE_SQL_PART_2 = ", {h-domain}"; + private static final String CUSTOM_DELETE_SQL_PART_3 = " WHERE id = ?"; + + enum SettingsMode { + // "Standard" way of providing settings, though configuration properties: + // both metadata and session factory receive the same settings + METADATA_SERVICE_REGISTRY, + // An alternative way of providing settings so that they are applied late, + // when the session factory is created. + // This mode is used by frameworks relying on build-time initialization of the application, + // like Quarkus and its "static init". + SESSION_FACTORY_SERVICE_REGISTRY + } + + @Parameterized.Parameters(name = "settingsMode = {0}, configuredXmlMappingPath = {1}, configuredDefaultCatalog = {2}, configuredDefaultSchema = {3}") + public static List params() { + List params = new ArrayList<>(); + for ( SettingsMode mode : SettingsMode.values() ) { + for ( String defaultCatalog : Arrays.asList( null, "someDefaultCatalog" ) ) { + for ( String defaultSchema : Arrays.asList( null, "someDefaultSchema" ) ) { + params.add( new Object[] { mode, null, defaultCatalog, defaultSchema, + // The default catalog/schema should be used when + // there is no implicit catalog/schema defined in the mapping. + defaultCatalog, defaultSchema } ); + } + } + params.add( new Object[] { mode, "implicit-global-catalog-and-schema.orm.xml", + null, null, + "someImplicitCatalog", "someImplicitSchema" } ); + // HHH-14922: Inconsistent precedence of orm.xml implicit catalog/schema over "default_catalog"/"default_schema" + params.add( new Object[] { mode, "implicit-global-catalog-and-schema.orm.xml", + "someDefaultCatalog", "someDefaultSchema", + // The default catalog/schema should replace the + // implicit catalog/schema defined in the mapping. + "someDefaultCatalog", "someDefaultSchema" } ); + } + return params; + } + + @Parameterized.Parameter + public SettingsMode settingsMode; + @Parameterized.Parameter(1) + public String configuredXmlMappingPath; + @Parameterized.Parameter(2) + public String configuredDefaultCatalog; + @Parameterized.Parameter(3) + public String configuredDefaultSchema; + @Parameterized.Parameter(4) + public String expectedDefaultCatalog; + @Parameterized.Parameter(5) + public String expectedDefaultSchema; + + private boolean dbSupportsCatalogs; + private boolean dbSupportsSchemas; + + private MetadataImplementor metadata; + private final List toClose = new ArrayList<>(); + private SessionFactoryImplementor sessionFactory; + + @BeforeClassOnce + public void initSessionFactory() { + List> annotatedClasses = Arrays.asList( + EntityWithDefaultQualifiers.class, + EntityWithExplicitQualifiers.class, + EntityWithJoinedInheritanceWithDefaultQualifiers.class, + EntityWithJoinedInheritanceWithDefaultQualifiersSubclass.class, + EntityWithJoinedInheritanceWithExplicitQualifiers.class, + EntityWithJoinedInheritanceWithExplicitQualifiersSubclass.class, + EntityWithTablePerClassInheritanceWithDefaultQualifiers.class, + EntityWithTablePerClassInheritanceWithDefaultQualifiersSubclass.class, + EntityWithTablePerClassInheritanceWithExplicitQualifiers.class, + EntityWithTablePerClassInheritanceWithExplicitQualifiersSubclass.class, + EntityWithDefaultQualifiersWithCustomSql.class, + EntityWithDefaultQualifiersWithIdentityGenerator.class, + EntityWithExplicitQualifiersWithIdentityGenerator.class, + EntityWithDefaultQualifiersWithTableGenerator.class, + EntityWithExplicitQualifiersWithTableGenerator.class, + EntityWithDefaultQualifiersWithSequenceGenerator.class, + EntityWithExplicitQualifiersWithSequenceGenerator.class, + EntityWithDefaultQualifiersWithSeqHiLoGenerator.class, + EntityWithExplicitQualifiersWithSeqHiLoGenerator.class, + EntityWithDefaultQualifiersWithIncrementGenerator.class, + EntityWithExplicitQualifiersWithIncrementGenerator.class, + EntityWithDefaultQualifiersWithSequenceIdentityGenerator.class, + EntityWithExplicitQualifiersWithSequenceIdentityGenerator.class, + EntityWithDefaultQualifiersWithEnhancedSequenceGenerator.class, + EntityWithExplicitQualifiersWithEnhancedSequenceGenerator.class, + EntityWithDefaultQualifiersWithLegacySequenceGenerator.class, + EntityWithExplicitQualifiersWithLegacySequenceGenerator.class + ); + + StandardServiceRegistry serviceRegistry; + switch ( settingsMode ) { + case METADATA_SERVICE_REGISTRY: + serviceRegistry = createStandardServiceRegistry( configuredDefaultCatalog, configuredDefaultSchema ); + break; + case SESSION_FACTORY_SERVICE_REGISTRY: + serviceRegistry = createStandardServiceRegistry( null, null ); + break; + default: + throw new IllegalStateException( "Unknown settings mode: " + settingsMode ); + } + + 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 ) ); + } + for ( Class annotatedClass : annotatedClasses ) { + metadataSources.addAnnotatedClass( annotatedClass ); + } + + final MetadataBuilder metadataBuilder = metadataSources.getMetadataBuilder(); + metadata = (MetadataImplementor) metadataBuilder.build(); + + SessionFactoryBuilder sfb; + switch ( settingsMode ) { + case METADATA_SERVICE_REGISTRY: + sfb = metadata.getSessionFactoryBuilder(); + break; + case SESSION_FACTORY_SERVICE_REGISTRY: + serviceRegistry = createStandardServiceRegistry( configuredDefaultCatalog, configuredDefaultSchema ); + sfb = new SessionFactoryBuilderImpl( metadata, new SessionFactoryOptionsBuilder( serviceRegistry, + ((MetadataImpl) metadata).getBootstrapContext() ) ); + break; + default: + throw new IllegalStateException( "Unknown settings mode: " + settingsMode ); + } + + sessionFactory = (SessionFactoryImplementor) sfb.build(); + toClose.add( sessionFactory ); + + NameQualifierSupport nameQualifierSupport = sessionFactory.getJdbcServices().getJdbcEnvironment() + .getNameQualifierSupport(); + dbSupportsCatalogs = nameQualifierSupport.supportsCatalogs(); + dbSupportsSchemas = nameQualifierSupport.supportsSchemas(); + } + + @AfterClassOnce + public void cleanup() throws Throwable { + Throwable thrown = null; + Collections.reverse( toClose ); + for ( AutoCloseable closeable : toClose ) { + try { + closeable.close(); + } + catch (Throwable t) { + if ( thrown == null ) { + thrown = t; + } + else { + thrown.addSuppressed( t ); + } + } + } + if ( thrown != null ) { + throw thrown; + } + } + + private StandardServiceRegistry createStandardServiceRegistry(String defaultCatalog, String defaultSchema) { + final BootstrapServiceRegistryBuilder bsrb = new BootstrapServiceRegistryBuilder(); + bsrb.applyClassLoader( getClass().getClassLoader() ); + // by default we do not share the BootstrapServiceRegistry nor the StandardServiceRegistry, + // so we want the BootstrapServiceRegistry to be automatically closed when the + // StandardServiceRegistry is closed. + bsrb.enableAutoClose(); + + final BootstrapServiceRegistry bsr = bsrb.build(); + + 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, + SharedDriverManagerConnectionProviderImpl.getInstance() + ); + } + if ( defaultCatalog != null ) { + settings.put( AvailableSettings.DEFAULT_CATALOG, defaultCatalog ); + } + if ( defaultSchema != null ) { + settings.put( AvailableSettings.DEFAULT_SCHEMA, defaultSchema ); + } + + final StandardServiceRegistryBuilder ssrb = new StandardServiceRegistryBuilder( bsr ); + ssrb.applySettings( settings ); + StandardServiceRegistry registry = ssrb.build(); + toClose.add( registry ); + return registry; + } + + @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 ); + } + + @Test + public void entityPersister() { + verifyEntityPersisterQualifiers( EntityWithDefaultQualifiers.class, expectedDefaultQualifier() ); + 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() ); + verifyEntityPersisterQualifiers( EntityWithJoinedInheritanceWithExplicitQualifiers.class, expectedExplicitQualifier() ); + verifyEntityPersisterQualifiers( EntityWithJoinedInheritanceWithExplicitQualifiersSubclass.class, expectedExplicitQualifier() ); + + verifyEntityPersisterQualifiers( EntityWithTablePerClassInheritanceWithDefaultQualifiers.class, expectedDefaultQualifier() ); + verifyEntityPersisterQualifiers( EntityWithTablePerClassInheritanceWithDefaultQualifiersSubclass.class, expectedDefaultQualifier() ); + verifyEntityPersisterQualifiers( EntityWithTablePerClassInheritanceWithExplicitQualifiers.class, expectedExplicitQualifier() ); + verifyEntityPersisterQualifiers( EntityWithTablePerClassInheritanceWithExplicitQualifiersSubclass.class, expectedExplicitQualifier() ); + + verifyEntityPersisterQualifiers( EntityWithDefaultQualifiersWithCustomSql.class, expectedDefaultQualifier() ); + + + verifyEntityPersisterQualifiers( EntityWithDefaultQualifiersWithIdentityGenerator.class, expectedDefaultQualifier() ); + verifyEntityPersisterQualifiers( EntityWithExplicitQualifiersWithIdentityGenerator.class, expectedExplicitQualifier() ); + verifyEntityPersisterQualifiers( EntityWithDefaultQualifiersWithTableGenerator.class, expectedDefaultQualifier() ); + verifyEntityPersisterQualifiers( EntityWithExplicitQualifiersWithTableGenerator.class, expectedExplicitQualifier() ); + verifyEntityPersisterQualifiers( EntityWithDefaultQualifiersWithSequenceGenerator.class, expectedDefaultQualifier() ); + verifyEntityPersisterQualifiers( EntityWithExplicitQualifiersWithSequenceGenerator.class, expectedExplicitQualifier() ); + verifyEntityPersisterQualifiers( EntityWithDefaultQualifiersWithSeqHiLoGenerator.class, expectedDefaultQualifier() ); + verifyEntityPersisterQualifiers( EntityWithExplicitQualifiersWithSeqHiLoGenerator.class, expectedExplicitQualifier() ); + verifyEntityPersisterQualifiers( EntityWithDefaultQualifiersWithIncrementGenerator.class, expectedDefaultQualifier() ); + verifyEntityPersisterQualifiers( EntityWithExplicitQualifiersWithIncrementGenerator.class, expectedExplicitQualifier() ); + verifyEntityPersisterQualifiers( EntityWithDefaultQualifiersWithSequenceIdentityGenerator.class, expectedDefaultQualifier() ); + verifyEntityPersisterQualifiers( EntityWithExplicitQualifiersWithSequenceIdentityGenerator.class, expectedExplicitQualifier() ); + verifyEntityPersisterQualifiers( EntityWithDefaultQualifiersWithEnhancedSequenceGenerator.class, expectedDefaultQualifier() ); + verifyEntityPersisterQualifiers( EntityWithExplicitQualifiersWithEnhancedSequenceGenerator.class, expectedExplicitQualifier() ); + verifyEntityPersisterQualifiers( EntityWithDefaultQualifiersWithLegacySequenceGenerator.class, expectedDefaultQualifier() ); + verifyEntityPersisterQualifiers( EntityWithExplicitQualifiersWithLegacySequenceGenerator.class, expectedExplicitQualifier() ); + } + + private void verifyEntityPersisterQualifiers(Class entityClass, ExpectedQualifier expectedQualifier) { + // The hbm.xml mapping unfortunately sets the native entity name on top of the JPA entity name, + // so many methods that allow retrieving the entity persister or entity metamodel from the entity class no longer work, + // because these methods generally assume the native entity name is the FQCN. + // Thus we use custom code. + AbstractEntityPersister persister = (AbstractEntityPersister) sessionFactory.getMetamodel().entityPersisters() + .values().stream() + .filter( p -> p.getMappedClass().equals( entityClass ) ) + .findFirst() + .orElseThrow( () -> new IllegalStateException( "Cannot find persister for " + entityClass ) ); + String jpaEntityName = sessionFactory.getMetamodel().getEntities().stream() + .filter( p -> p.getBindableJavaType().equals( entityClass ) ) + .findFirst() + .orElseThrow( () -> new IllegalStateException( "Cannot find entity metamodel for " + entityClass ) ) + .getName(); + + // Table names are what's used for Query, in particular. + verifyOnlyQualifier( persister.getTableName(), SqlType.RUNTIME, + jpaEntityName, expectedQualifier ); + // Here, to simplify assertions, we assume all derived entity types have: + // - an entity name prefixed with the name of their super entity type + // - the same explicit catalog and schema, if any, as their super entity type + verifyOnlyQualifier( persister.getTableNames(), SqlType.RUNTIME, + jpaEntityName, expectedQualifier ); + + // This will include SQL generated by ID generators in some cases, which will be validated here + // because ID generators table/sequence names are prefixed with the owning entity name. + verifyOnlyQualifier( persister.getSQLInsertStrings(), SqlType.RUNTIME, + jpaEntityName, expectedQualifier ); + if ( persister.isIdentifierAssignedByInsert() ) { + verifyOnlyQualifier( persister.getSQLIdentityInsertString(), SqlType.RUNTIME, + jpaEntityName, expectedQualifier ); + } + try { + verifyOnlyQualifierOptional( persister.getIdentitySelectString(), SqlType.RUNTIME, + jpaEntityName, expectedQualifier ); + } + catch (MappingException e) { + if ( e.getMessage().contains( "does not support identity key generation" ) ) { + // For some reason Oracle12cIdentityColumnSupport#supportsInsertSelectIdentity() returns true, + // but getIdentitySelectString is not implemented, resulting in runtime exceptions. + // Whatever, we'll just ignore this for now. + } + else { + throw e; + } + } + + verifyOnlyQualifier( persister.getSQLUpdateStrings(), SqlType.RUNTIME, + jpaEntityName, expectedQualifier ); + verifyOnlyQualifier( persister.getSQLLazyUpdateStrings(), SqlType.RUNTIME, + jpaEntityName, expectedQualifier ); + + verifyOnlyQualifier( persister.getSQLDeleteStrings(), SqlType.RUNTIME, + jpaEntityName, expectedQualifier ); + + verifyOnlyQualifier( persister.getSQLSnapshotSelectString(), SqlType.RUNTIME, + jpaEntityName, expectedQualifier ); + + // This is used in the "select" id generator in particular. + verifyOnlyQualifierOptional( persister.getSelectByUniqueKeyString( "basic" ), SqlType.RUNTIME, + jpaEntityName, expectedQualifier ); + } + + @Test + public void tableGenerator() { + org.hibernate.id.enhanced.TableGenerator generator = idGenerator( + org.hibernate.id.enhanced.TableGenerator.class, + EntityWithDefaultQualifiersWithTableGenerator.class ); + verifyOnlyQualifier( generator.getAllSqlForTests(), SqlType.RUNTIME, + EntityWithDefaultQualifiersWithTableGenerator.NAME, expectedDefaultQualifier() ); + + generator = idGenerator( org.hibernate.id.enhanced.TableGenerator.class, + EntityWithExplicitQualifiersWithTableGenerator.class ); + verifyOnlyQualifier( generator.getAllSqlForTests(), SqlType.RUNTIME, + EntityWithExplicitQualifiersWithTableGenerator.NAME, expectedExplicitQualifier() ); + } + + @Test + public void enhancedTableGenerator() { + org.hibernate.id.enhanced.TableGenerator generator = idGenerator( + org.hibernate.id.enhanced.TableGenerator.class, + EntityWithDefaultQualifiersWithTableGenerator.class ); + verifyOnlyQualifier( generator.getAllSqlForTests(), SqlType.RUNTIME, + EntityWithDefaultQualifiersWithTableGenerator.NAME, expectedDefaultQualifier() ); + + generator = idGenerator( org.hibernate.id.enhanced.TableGenerator.class, + EntityWithExplicitQualifiersWithTableGenerator.class ); + verifyOnlyQualifier( generator.getAllSqlForTests(), SqlType.RUNTIME, + EntityWithExplicitQualifiersWithTableGenerator.NAME, expectedExplicitQualifier() ); + } + + @Test + public void sequenceGenerator() { + org.hibernate.id.enhanced.SequenceStyleGenerator generator = idGenerator( + org.hibernate.id.enhanced.SequenceStyleGenerator.class, + EntityWithDefaultQualifiersWithSequenceGenerator.class ); + verifyOnlyQualifier( generator.getDatabaseStructure().getAllSqlForTests(), SqlType.RUNTIME, + EntityWithDefaultQualifiersWithSequenceGenerator.NAME, expectedDefaultQualifier() ); + + generator = idGenerator( org.hibernate.id.enhanced.SequenceStyleGenerator.class, + EntityWithExplicitQualifiersWithSequenceGenerator.class ); + verifyOnlyQualifier( generator.getDatabaseStructure().getAllSqlForTests(), SqlType.RUNTIME, + EntityWithExplicitQualifiersWithSequenceGenerator.NAME, expectedExplicitQualifier() ); + } + + @Test + public void enhancedSequenceGenerator() { + org.hibernate.id.enhanced.SequenceStyleGenerator generator = idGenerator( + org.hibernate.id.enhanced.SequenceStyleGenerator.class, + EntityWithDefaultQualifiersWithEnhancedSequenceGenerator.class ); + verifyOnlyQualifier( generator.getDatabaseStructure().getAllSqlForTests(), SqlType.RUNTIME, + EntityWithDefaultQualifiersWithEnhancedSequenceGenerator.NAME, expectedDefaultQualifier() ); + + generator = idGenerator( org.hibernate.id.enhanced.SequenceStyleGenerator.class, + EntityWithExplicitQualifiersWithEnhancedSequenceGenerator.class ); + verifyOnlyQualifier( generator.getDatabaseStructure().getAllSqlForTests(), SqlType.RUNTIME, + EntityWithExplicitQualifiersWithEnhancedSequenceGenerator.NAME, expectedExplicitQualifier() ); + } + + @Test + public void legacySequenceGenerator() { + org.hibernate.id.SequenceGenerator generator = idGenerator( org.hibernate.id.SequenceGenerator.class, + EntityWithDefaultQualifiersWithLegacySequenceGenerator.class ); + verifyOnlyQualifier( generator.getAllSqlForTests(), SqlType.RUNTIME, + EntityWithDefaultQualifiersWithLegacySequenceGenerator.NAME, expectedDefaultQualifier() ); + + generator = idGenerator( org.hibernate.id.SequenceGenerator.class, + EntityWithExplicitQualifiersWithLegacySequenceGenerator.class ); + verifyOnlyQualifier( generator.getAllSqlForTests(), SqlType.RUNTIME, + EntityWithExplicitQualifiersWithLegacySequenceGenerator.NAME, expectedExplicitQualifier() ); + } + + @Test + public void seqHiLoGenerator() { + org.hibernate.id.SequenceHiLoGenerator generator = idGenerator( org.hibernate.id.SequenceHiLoGenerator.class, + EntityWithDefaultQualifiersWithSeqHiLoGenerator.class ); + verifyOnlyQualifier( generator.getAllSqlForTests(), SqlType.RUNTIME, + EntityWithDefaultQualifiersWithSeqHiLoGenerator.NAME, expectedDefaultQualifier() ); + + generator = idGenerator( org.hibernate.id.SequenceHiLoGenerator.class, + EntityWithExplicitQualifiersWithSeqHiLoGenerator.class ); + verifyOnlyQualifier( generator.getAllSqlForTests(), SqlType.RUNTIME, + EntityWithExplicitQualifiersWithSeqHiLoGenerator.NAME, expectedExplicitQualifier() ); + } + + @Test + public void incrementGenerator() { + org.hibernate.id.IncrementGenerator generator = idGenerator( org.hibernate.id.IncrementGenerator.class, + EntityWithDefaultQualifiersWithIncrementGenerator.class ); + verifyOnlyQualifier( generator.getAllSqlForTests(), SqlType.RUNTIME, + EntityWithDefaultQualifiersWithIncrementGenerator.NAME, expectedDefaultQualifier() ); + + generator = idGenerator( org.hibernate.id.IncrementGenerator.class, + EntityWithExplicitQualifiersWithIncrementGenerator.class ); + verifyOnlyQualifier( generator.getAllSqlForTests(), SqlType.RUNTIME, + EntityWithExplicitQualifiersWithIncrementGenerator.NAME, expectedExplicitQualifier() ); + } + + @Test + public void sequenceIdentityGenerator() { + org.hibernate.id.SequenceIdentityGenerator generator = idGenerator( org.hibernate.id.SequenceIdentityGenerator.class, + EntityWithDefaultQualifiersWithSequenceIdentityGenerator.class ); + verifyOnlyQualifier( generator.getAllSqlForTests(), SqlType.RUNTIME, + EntityWithDefaultQualifiersWithSequenceIdentityGenerator.NAME, expectedDefaultQualifier() ); + + generator = idGenerator( org.hibernate.id.SequenceIdentityGenerator.class, + EntityWithExplicitQualifiersWithSequenceIdentityGenerator.class ); + verifyOnlyQualifier( generator.getAllSqlForTests(), SqlType.RUNTIME, + EntityWithExplicitQualifiersWithSequenceIdentityGenerator.NAME, expectedExplicitQualifier() ); + } + + private T idGenerator(Class expectedType, Class entityClass) { + AbstractEntityPersister persister = (AbstractEntityPersister) + sessionFactory.getMetamodel().entityPersister( entityClass ); + 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 + // - that all association tables have a name prefixed with the name of their owning entity type + // - that all association tables have the same explicit catalog and schema, if any, as their owning entity type + // - that all ID generator tables/sequences have a name prefixed with the name of their owning entity type + + verifyOnlyQualifier( sql, SqlType.DDL, EntityWithExplicitQualifiers.NAME, expectedExplicitQualifier() ); + 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() ); + verifyOnlyQualifier( sql, SqlType.DDL, EntityWithJoinedInheritanceWithExplicitQualifiers.NAME, expectedExplicitQualifier() ); + verifyOnlyQualifier( sql, SqlType.DDL, EntityWithJoinedInheritanceWithExplicitQualifiersSubclass.NAME, expectedExplicitQualifier() ); + + verifyOnlyQualifier( sql, SqlType.DDL, EntityWithTablePerClassInheritanceWithDefaultQualifiers.NAME, expectedDefaultQualifier() ); + verifyOnlyQualifier( sql, SqlType.DDL, EntityWithTablePerClassInheritanceWithDefaultQualifiersSubclass.NAME, expectedDefaultQualifier() ); + verifyOnlyQualifier( sql, SqlType.DDL, EntityWithTablePerClassInheritanceWithExplicitQualifiers.NAME, expectedExplicitQualifier() ); + verifyOnlyQualifier( sql, SqlType.DDL, EntityWithTablePerClassInheritanceWithExplicitQualifiersSubclass.NAME, expectedExplicitQualifier() ); + + verifyOnlyQualifier( sql, SqlType.DDL, EntityWithDefaultQualifiersWithCustomSql.NAME, expectedDefaultQualifier() ); + + verifyOnlyQualifier( sql, SqlType.DDL, EntityWithDefaultQualifiersWithIdentityGenerator.NAME, expectedDefaultQualifier() ); + verifyOnlyQualifier( sql, SqlType.DDL, EntityWithExplicitQualifiersWithIdentityGenerator.NAME, expectedExplicitQualifier() ); + verifyOnlyQualifier( sql, SqlType.DDL, EntityWithDefaultQualifiersWithTableGenerator.NAME, expectedDefaultQualifier() ); + verifyOnlyQualifier( sql, SqlType.DDL, EntityWithExplicitQualifiersWithTableGenerator.NAME, expectedExplicitQualifier() ); + verifyOnlyQualifier( sql, SqlType.DDL, EntityWithDefaultQualifiersWithSequenceGenerator.NAME, expectedDefaultQualifier() ); + verifyOnlyQualifier( sql, SqlType.DDL, EntityWithExplicitQualifiersWithSequenceGenerator.NAME, expectedExplicitQualifier() ); + verifyOnlyQualifier( sql, SqlType.DDL, EntityWithDefaultQualifiersWithSeqHiLoGenerator.NAME, expectedDefaultQualifier() ); + verifyOnlyQualifier( sql, SqlType.DDL, EntityWithExplicitQualifiersWithSeqHiLoGenerator.NAME, expectedExplicitQualifier() ); + verifyOnlyQualifier( sql, SqlType.DDL, EntityWithDefaultQualifiersWithIncrementGenerator.NAME, expectedDefaultQualifier() ); + verifyOnlyQualifier( sql, SqlType.DDL, EntityWithExplicitQualifiersWithIncrementGenerator.NAME, expectedExplicitQualifier() ); + verifyOnlyQualifier( sql, SqlType.DDL, EntityWithDefaultQualifiersWithSequenceIdentityGenerator.NAME, expectedDefaultQualifier() ); + verifyOnlyQualifier( sql, SqlType.DDL, EntityWithExplicitQualifiersWithSequenceIdentityGenerator.NAME, expectedExplicitQualifier() ); + verifyOnlyQualifier( sql, SqlType.DDL, EntityWithDefaultQualifiersWithEnhancedSequenceGenerator.NAME, expectedDefaultQualifier() ); + 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 { + DDL, + RUNTIME + } + + private void verifyOnlyQualifier(String[] sql, SqlType sqlType, String name, ExpectedQualifier expectedQualifier) { + verifyOnlyQualifier( String.join( "\n", sql ), sqlType, name, expectedQualifier ); + } + + private void verifyOnlyQualifierOptional(String sql, SqlType sqlType, String name, ExpectedQualifier expectedQualifier) { + verifyOnlyQualifier( sql, sqlType, name, expectedQualifier, true ); + } + + private void verifyOnlyQualifier(String sql, SqlType sqlType, String name, ExpectedQualifier expectedQualifier) { + verifyOnlyQualifier( sql, sqlType, name, expectedQualifier, false ); + } + + private void verifyOnlyQualifier(String sql, SqlType sqlType, String name, ExpectedQualifier expectedQualifier, boolean optional) { + String patternStringForTableName = SQL_QUOTE_CHARACTER_CLASS + "?" + Pattern.quote( name ) + "(?!\\w*_seq)" + SQL_QUOTE_CHARACTER_CLASS + "?"; + String patternStringForSequenceName = SQL_QUOTE_CHARACTER_CLASS + "?" + Pattern.quote( name ) + "\\w*_seq" + SQL_QUOTE_CHARACTER_CLASS + "?"; + + ExpectedQualifier expectedQualifierForTables = expectedQualifier; + ExpectedQualifier expectedQualifierForSequences; + if ( SqlType.DDL == sqlType && sessionFactory.getJdbcServices().getDialect() instanceof SQLServer2012Dialect ) { + // SQL Server does not allow the catalog in the sequence name when creating the sequence, + // so we need different patterns for sequence names and table names. + // See org.hibernate.dialect.SQLServer2012Dialect.SqlServerSequenceExporter.getFormattedSequenceName + expectedQualifierForSequences = new ExpectedQualifier( null, expectedQualifier.schema ); + } + else { + expectedQualifierForSequences = expectedQualifier; + } + + if ( !optional ) { + // Check that we find the name at least once with the proper qualifier, be it a table or a sequence. + // While not strictly necessary, this ensures our patterns are not completely wrong. + assertThat( sql ) + .containsPattern( + "(" + expectedQualifierForTables.patternStringForNameWithThisQualifier( patternStringForTableName ) + + ")|(" + + expectedQualifierForSequences.patternStringForNameWithThisQualifier( patternStringForSequenceName ) + + ")" ); + } + + // Check that we don't find any name with an incorrect qualifier + assertThat( sql.split( System.getProperty( "line.separator" ) ) ) + .allSatisfy( line -> assertThat( line ) + .doesNotContainPattern( expectedQualifierForTables + .patternStringForNameWithDifferentQualifier( patternStringForTableName ) ) + .doesNotContainPattern( expectedQualifierForSequences + .patternStringForNameWithDifferentQualifier( patternStringForSequenceName ) ) ); + } + + private ExpectedQualifier expectedDefaultQualifier() { + return expectedQualifier( expectedDefaultCatalog, expectedDefaultSchema ); + } + + private ExpectedQualifier expectedExplicitQualifier() { + return expectedQualifier( EXPLICIT_CATALOG, EXPLICIT_SCHEMA ); + } + + private ExpectedQualifier expectedImplicitFileLevelQualifier() { + return expectedQualifier( IMPLICIT_FILE_LEVEL_CATALOG, IMPLICIT_FILE_LEVEL_SCHEMA ); + } + + private ExpectedQualifier expectedQualifier(String catalog, String schema) { + return new ExpectedQualifier( + dbSupportsCatalogs ? catalog : null, + dbSupportsSchemas ? schema : null + ); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private String generateScriptFromSessionFactory(String action) { + ServiceRegistryImplementor serviceRegistry = sessionFactory.getServiceRegistry(); + Map settings = new HashMap<>( + serviceRegistry.getService( ConfigurationService.class ).getSettings() + ); + StringWriter writer = new StringWriter(); + settings.put( AvailableSettings.HBM2DDL_SCRIPTS_ACTION, action ); + settings.put( AvailableSettings.HBM2DDL_SCRIPTS_CREATE_TARGET, writer ); + settings.put( AvailableSettings.HBM2DDL_SCRIPTS_DROP_TARGET, writer ); + + SchemaManagementToolCoordinator.process( + metadata, serviceRegistry, settings, DelayedDropRegistryNotAvailableImpl.INSTANCE ); + return writer.toString(); + } + + // This is precisely how scripts are generated for the Quarkus DevUI + // Don't change this code except to match changes in + // https://github.com/quarkusio/quarkus/blob/d07ecb23bfba38ee48868635e155c4b513ce6af9/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/devconsole/HibernateOrmDevConsoleInfoSupplier.java#L61-L92 + private String generateScriptFromMetadata(SchemaExport.Action action) { + ServiceRegistryImplementor sessionFactoryServiceRegistry = sessionFactory.getServiceRegistry(); + SchemaExport schemaExport = new SchemaExport(); + schemaExport.setFormat( true ); + schemaExport.setDelimiter( ";" ); + StringWriter writer = new StringWriter(); + schemaExport.doExecution( action, false, metadata, sessionFactoryServiceRegistry, + new TargetDescriptor() { + @Override + public EnumSet getTargetTypes() { + return EnumSet.of( TargetType.SCRIPT ); + } + + @Override + public ScriptTargetOutput getScriptTargetOutput() { + return new ScriptTargetOutputToWriter( writer ) { + @Override + public void accept(String command) { + super.accept( command ); + } + }; + } + } + ); + return writer.toString(); + } + + private static class ExpectedQualifier { + private final String catalog; + private final String schema; + + private ExpectedQualifier(String catalog, String schema) { + this.catalog = catalog; + this.schema = schema; + } + + String patternStringForNameWithThisQualifier(String patternStringForName) { + if ( catalog == null && schema == null ) { + // Look for unqualified names + return "(? oneToMany; + @OneToMany + @JoinTable(name = NAME + "_manyToMany", + // Custom names to avoid false positive in assertions + joinColumns = @JoinColumn(name = "forward"), + inverseJoinColumns = @JoinColumn(name = "inverse"), + foreignKey = @ForeignKey(name = "FK_manyToMany")) + private List manyToMany; + @ElementCollection + @JoinTable(name = NAME + "_elementCollection", + // Custom names to avoid false positive in assertions + joinColumns = @JoinColumn(name = "forward"), + foreignKey = @ForeignKey(name = "FK_elementCollection")) + private List elementCollection; + } + + @Entity(name = EntityWithExplicitQualifiers.NAME) + @Table(catalog = EXPLICIT_CATALOG, schema = EXPLICIT_SCHEMA) + public static class EntityWithExplicitQualifiers { + public static final String NAME = "EntityWithExplicitQualifiers"; + @Id + private Long id; + @Basic + private String basic; + @OneToMany + @JoinTable(name = NAME + "_oneToMany", catalog = EXPLICIT_CATALOG, schema = EXPLICIT_SCHEMA, + // Custom names to avoid false positive in assertions + joinColumns = @JoinColumn(name = "forward"), + inverseJoinColumns = @JoinColumn(name = "inverse"), + foreignKey = @ForeignKey(name = "FK_oneToMany")) + private List oneToMany; + @OneToMany + @JoinTable(name = NAME + "_manyToMany", catalog = EXPLICIT_CATALOG, schema = EXPLICIT_SCHEMA, + // Custom names to avoid false positive in assertions + joinColumns = @JoinColumn(name = "forward"), + inverseJoinColumns = @JoinColumn(name = "inverse"), + foreignKey = @ForeignKey(name = "FK_manyToMany")) + private List manyToMany; + @ElementCollection + @JoinTable(name = NAME + "_elementCollection", catalog = EXPLICIT_CATALOG, schema = EXPLICIT_SCHEMA, + // Custom names to avoid false positive in assertions + joinColumns = @JoinColumn(name = "forward"), + foreignKey = @ForeignKey(name = "FK_elementCollection")) + private List elementCollection; + } + + public static class EntityWithOrmXmlImplicitFileLevelQualifiers { + public static final String NAME = "EntityWithOrmXmlImplicitFileLevelQualifiers"; + private Long id; + private String basic; + private List oneToMany; + private List manyToMany; + private List elementCollection; + } + + public static class EntityWithHbmXmlImplicitFileLevelQualifiers { + public static final String NAME = "EntityWithHbmXmlImplicitFileLevelQualifiers"; + private Long id; + 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 { + public static final String NAME = "EntityWithJoinedInheritanceWithDefaultQualifiers"; + @Id + private Long id; + @Basic + private String basic; + @OneToMany + @JoinTable(name = NAME + "_oneToMany", + // Custom names to avoid false positive in assertions + joinColumns = @JoinColumn(name = "forward"), + inverseJoinColumns = @JoinColumn(name = "inverse"), + foreignKey = @ForeignKey(name = "FK_oneToMany")) + private List oneToMany; + @OneToMany + @JoinTable(name = NAME + "_manyToMany", + // Custom names to avoid false positive in assertions + joinColumns = @JoinColumn(name = "forward"), + inverseJoinColumns = @JoinColumn(name = "inverse"), + foreignKey = @ForeignKey(name = "FK_manyToMany")) + private List manyToMany; + @ElementCollection + @JoinTable(name = NAME + "_elementCollection", + // Custom names to avoid false positive in assertions + joinColumns = @JoinColumn(name = "forward"), + foreignKey = @ForeignKey(name = "FK_elementCollection")) + private List elementCollection; + } + + @Entity(name = EntityWithJoinedInheritanceWithDefaultQualifiersSubclass.NAME) + public static class EntityWithJoinedInheritanceWithDefaultQualifiersSubclass + extends EntityWithJoinedInheritanceWithDefaultQualifiers { + public static final String NAME = "EntityWithJoinedInheritanceWithDefaultQualifiersSubclass"; + @Basic + private String basic2; + } + + @Entity(name = EntityWithJoinedInheritanceWithExplicitQualifiers.NAME) + @Inheritance(strategy = InheritanceType.JOINED) + @Table(catalog = EXPLICIT_CATALOG, schema = EXPLICIT_SCHEMA) + public static class EntityWithJoinedInheritanceWithExplicitQualifiers { + public static final String NAME = "EntityWithJoinedInheritanceWithExplicitQualifiers"; + @Id + private Long id; + @Basic + private String basic; + @OneToMany + @JoinTable(name = NAME + "_oneToMany", catalog = EXPLICIT_CATALOG, schema = EXPLICIT_SCHEMA, + // Custom names to avoid false positive in assertions + joinColumns = @JoinColumn(name = "forward"), + inverseJoinColumns = @JoinColumn(name = "inverse"), + foreignKey = @ForeignKey(name = "FK_oneToMany")) + private List oneToMany; + @OneToMany + @JoinTable(name = NAME + "_manyToMany", catalog = EXPLICIT_CATALOG, schema = EXPLICIT_SCHEMA, + // Custom names to avoid false positive in assertions + joinColumns = @JoinColumn(name = "forward"), + inverseJoinColumns = @JoinColumn(name = "inverse"), + foreignKey = @ForeignKey(name = "FK_manyToMany")) + private List manyToMany; + @ElementCollection + @JoinTable(name = NAME + "_elementCollection", catalog = EXPLICIT_CATALOG, schema = EXPLICIT_SCHEMA, + // Custom names to avoid false positive in assertions + joinColumns = @JoinColumn(name = "forward"), + foreignKey = @ForeignKey(name = "FK_elementCollection")) + private List elementCollection; + } + + @Entity(name = EntityWithJoinedInheritanceWithExplicitQualifiersSubclass.NAME) + @Table(catalog = EXPLICIT_CATALOG, schema = EXPLICIT_SCHEMA) + public static class EntityWithJoinedInheritanceWithExplicitQualifiersSubclass + extends EntityWithJoinedInheritanceWithExplicitQualifiers { + public static final String NAME = "EntityWithJoinedInheritanceWithExplicitQualifiersSubclass"; + @Basic + private String basic2; + } + + @Entity(name = EntityWithTablePerClassInheritanceWithDefaultQualifiers.NAME) + @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) + public static class EntityWithTablePerClassInheritanceWithDefaultQualifiers { + public static final String NAME = "EntityWithTablePerClassInheritanceWithDefaultQualifiers"; + @Id + private Long id; + @Basic + private String basic; + @OneToMany + @JoinTable(name = NAME + "_oneToMany", + // Custom names to avoid false positive in assertions + joinColumns = @JoinColumn(name = "forward"), + inverseJoinColumns = @JoinColumn(name = "inverse"), + foreignKey = @ForeignKey(name = "FK_oneToMany")) + private List oneToMany; + @OneToMany + @JoinTable(name = NAME + "_manyToMany", + // Custom names to avoid false positive in assertions + joinColumns = @JoinColumn(name = "forward"), + inverseJoinColumns = @JoinColumn(name = "inverse"), + foreignKey = @ForeignKey(name = "FK_manyToMany")) + private List manyToMany; + @ElementCollection + @JoinTable(name = NAME + "_elementCollection", + // Custom names to avoid false positive in assertions + joinColumns = @JoinColumn(name = "forward"), + foreignKey = @ForeignKey(name = "FK_elementCollection")) + private List elementCollection; + } + + @Entity(name = EntityWithTablePerClassInheritanceWithDefaultQualifiersSubclass.NAME) + public static class EntityWithTablePerClassInheritanceWithDefaultQualifiersSubclass + extends EntityWithTablePerClassInheritanceWithDefaultQualifiers { + public static final String NAME = "EntityWithTablePerClassInheritanceWithDefaultQualifiersSubclass"; + @Basic + private String basic2; + } + + @Entity(name = EntityWithTablePerClassInheritanceWithExplicitQualifiers.NAME) + @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) + @Table(catalog = EXPLICIT_CATALOG, schema = EXPLICIT_SCHEMA) + public static class EntityWithTablePerClassInheritanceWithExplicitQualifiers { + public static final String NAME = "EntityWithTablePerClassInheritanceWithExplicitQualifiers"; + @Id + private Long id; + @Basic + private String basic; + @OneToMany + @JoinTable(name = NAME + "_oneToMany", catalog = EXPLICIT_CATALOG, schema = EXPLICIT_SCHEMA, + // Custom names to avoid false positive in assertions + joinColumns = @JoinColumn(name = "forward"), + inverseJoinColumns = @JoinColumn(name = "inverse"), + foreignKey = @ForeignKey(name = "FK_oneToMany")) + private List oneToMany; + @OneToMany + @JoinTable(name = NAME + "_manyToMany", catalog = EXPLICIT_CATALOG, schema = EXPLICIT_SCHEMA, + // Custom names to avoid false positive in assertions + joinColumns = @JoinColumn(name = "forward"), + inverseJoinColumns = @JoinColumn(name = "inverse"), + foreignKey = @ForeignKey(name = "FK_manyToMany")) + private List manyToMany; + @ElementCollection + @JoinTable(name = NAME + "_elementCollection", catalog = EXPLICIT_CATALOG, schema = EXPLICIT_SCHEMA, + // Custom names to avoid false positive in assertions + joinColumns = @JoinColumn(name = "forward"), + foreignKey = @ForeignKey(name = "FK_elementCollection")) + private List elementCollection; + } + + @Entity(name = EntityWithTablePerClassInheritanceWithExplicitQualifiersSubclass.NAME) + @Table(catalog = EXPLICIT_CATALOG, schema = EXPLICIT_SCHEMA) + public static class EntityWithTablePerClassInheritanceWithExplicitQualifiersSubclass + extends EntityWithTablePerClassInheritanceWithExplicitQualifiers { + public static final String NAME = "EntityWithTablePerClassInheritanceWithExplicitQualifiersSubclass"; + @Basic + private String basic2; + } + + @Entity(name = EntityWithDefaultQualifiersWithCustomSql.NAME) + @SQLInsert(sql = CUSTOM_INSERT_SQL_PART_1 + EntityWithDefaultQualifiersWithCustomSql.NAME + CUSTOM_INSERT_SQL_PART_2 + + EntityWithDefaultQualifiersWithCustomSql.NAME + CUSTOM_INSERT_SQL_PART_3) + @SQLUpdate(sql = CUSTOM_UPDATE_SQL_PART_1 + EntityWithDefaultQualifiersWithCustomSql.NAME + CUSTOM_UPDATE_SQL_PART_2 + + EntityWithDefaultQualifiersWithCustomSql.NAME + CUSTOM_UPDATE_SQL_PART_3) + @SQLDelete(sql = CUSTOM_DELETE_SQL_PART_1 + EntityWithDefaultQualifiersWithCustomSql.NAME + CUSTOM_DELETE_SQL_PART_2 + + EntityWithDefaultQualifiersWithCustomSql.NAME + CUSTOM_DELETE_SQL_PART_3) + public static class EntityWithDefaultQualifiersWithCustomSql { + public static final String NAME = "EntityWithDefaultQualifiersWithCustomSql"; + @Id + private Long id; + @Basic + private String basic; + @OneToMany + @JoinTable(name = NAME + "_oneToMany", + // Custom names to avoid false positive in assertions + joinColumns = @JoinColumn(name = "forward"), + inverseJoinColumns = @JoinColumn(name = "inverse"), + foreignKey = @ForeignKey(name = "FK_oneToMany")) + @SQLInsert(sql = CUSTOM_INSERT_SQL_PART_1 + EntityWithDefaultQualifiersWithCustomSql.NAME + "_oneToMany" + CUSTOM_INSERT_SQL_PART_2 + + EntityWithDefaultQualifiersWithCustomSql.NAME + "_oneToMany" + CUSTOM_INSERT_SQL_PART_3) + @SQLUpdate(sql = CUSTOM_UPDATE_SQL_PART_1 + EntityWithDefaultQualifiersWithCustomSql.NAME + "_oneToMany" + CUSTOM_UPDATE_SQL_PART_2 + + EntityWithDefaultQualifiersWithCustomSql.NAME + "_oneToMany" + CUSTOM_UPDATE_SQL_PART_3) + @SQLDelete(sql = CUSTOM_DELETE_SQL_PART_1 + EntityWithDefaultQualifiersWithCustomSql.NAME + "_oneToMany" + CUSTOM_DELETE_SQL_PART_2 + + EntityWithDefaultQualifiersWithCustomSql.NAME + "_oneToMany" + CUSTOM_DELETE_SQL_PART_3) + private List oneToMany; + @OneToMany + @JoinTable(name = NAME + "_manyToMany", + // Custom names to avoid false positive in assertions + joinColumns = @JoinColumn(name = "forward"), + inverseJoinColumns = @JoinColumn(name = "inverse"), + foreignKey = @ForeignKey(name = "FK_manyToMany")) + @SQLInsert(sql = CUSTOM_INSERT_SQL_PART_1 + EntityWithDefaultQualifiersWithCustomSql.NAME + "_manyToMany" + CUSTOM_INSERT_SQL_PART_2 + + EntityWithDefaultQualifiersWithCustomSql.NAME + "_manyToMany" + CUSTOM_INSERT_SQL_PART_3) + @SQLUpdate(sql = CUSTOM_UPDATE_SQL_PART_1 + EntityWithDefaultQualifiersWithCustomSql.NAME + "_manyToMany" + CUSTOM_UPDATE_SQL_PART_2 + + EntityWithDefaultQualifiersWithCustomSql.NAME + "_manyToMany" + CUSTOM_UPDATE_SQL_PART_3) + @SQLDelete(sql = CUSTOM_DELETE_SQL_PART_1 + EntityWithDefaultQualifiersWithCustomSql.NAME + "_manyToMany" + CUSTOM_DELETE_SQL_PART_2 + + EntityWithDefaultQualifiersWithCustomSql.NAME + "_manyToMany" + CUSTOM_DELETE_SQL_PART_3) + private List manyToMany; + @ElementCollection + @JoinTable(name = NAME + "_elementCollection", + // Custom names to avoid false positive in assertions + joinColumns = @JoinColumn(name = "forward"), + foreignKey = @ForeignKey(name = "FK_elementCollection")) + @SQLInsert(sql = CUSTOM_INSERT_SQL_PART_1 + EntityWithDefaultQualifiersWithCustomSql.NAME + "_elementCollection" + CUSTOM_INSERT_SQL_PART_2 + + EntityWithDefaultQualifiersWithCustomSql.NAME + "_elementCollection" + CUSTOM_INSERT_SQL_PART_3) + @SQLUpdate(sql = CUSTOM_UPDATE_SQL_PART_1 + EntityWithDefaultQualifiersWithCustomSql.NAME + "_elementCollection" + CUSTOM_UPDATE_SQL_PART_2 + + EntityWithDefaultQualifiersWithCustomSql.NAME + "_elementCollection" + CUSTOM_UPDATE_SQL_PART_3) + @SQLDelete(sql = CUSTOM_DELETE_SQL_PART_1 + EntityWithDefaultQualifiersWithCustomSql.NAME + "_elementCollection" + CUSTOM_DELETE_SQL_PART_2 + + EntityWithDefaultQualifiersWithCustomSql.NAME + "_elementCollection" + CUSTOM_DELETE_SQL_PART_3) + private List elementCollection; + } + + @Entity(name = EntityWithDefaultQualifiersWithIdentityGenerator.NAME) + public static class EntityWithDefaultQualifiersWithIdentityGenerator { + public static final String NAME = "EntityWithDefaultQualifiersWithIdentityGenerator"; + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + @Basic + private String basic; + } + + @Entity(name = EntityWithExplicitQualifiersWithIdentityGenerator.NAME) + @Table(catalog = EXPLICIT_CATALOG, schema = EXPLICIT_SCHEMA) + public static class EntityWithExplicitQualifiersWithIdentityGenerator { + public static final String NAME = "EntityWithExplicitQualifiersWithIdentityGenerator"; + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + @Basic + private String basic; + } + + @Entity(name = EntityWithDefaultQualifiersWithSequenceGenerator.NAME) + public static class EntityWithDefaultQualifiersWithSequenceGenerator { + public static final String NAME = "EntityWithDefaultQualifiersWithSequenceGenerator"; + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = NAME + "_generator") + @SequenceGenerator(name = NAME + "_generator", sequenceName = NAME + "_seq") + private Long id; + @Basic + private String basic; + } + + @Entity(name = EntityWithExplicitQualifiersWithSequenceGenerator.NAME) + @Table(catalog = EXPLICIT_CATALOG, schema = EXPLICIT_SCHEMA) + public static class EntityWithExplicitQualifiersWithSequenceGenerator { + public static final String NAME = "EntityWithExplicitQualifiersWithSequenceGenerator"; + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = NAME + "_generator") + @SequenceGenerator(name = NAME + "_generator", sequenceName = NAME + "_seq", + catalog = EXPLICIT_CATALOG, schema = EXPLICIT_SCHEMA) + private Long id; + @Basic + private String basic; + } + + + @Entity(name = EntityWithDefaultQualifiersWithTableGenerator.NAME) + public static class EntityWithDefaultQualifiersWithTableGenerator { + public static final String NAME = "EntityWithDefaultQualifiersWithTableGenerator"; + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = NAME + "_generator") + @TableGenerator(name = NAME + "_generator", table = NAME + "_tableseq") + private Long id; + @Basic + private String basic; + } + + @Entity(name = EntityWithExplicitQualifiersWithTableGenerator.NAME) + @Table(catalog = EXPLICIT_CATALOG, schema = EXPLICIT_SCHEMA) + public static class EntityWithExplicitQualifiersWithTableGenerator { + public static final String NAME = "EntityWithExplicitQualifiersWithTableGenerator"; + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = NAME + "_generator") + @TableGenerator(name = NAME + "_generator", table = NAME + "_tableseq", + catalog = EXPLICIT_CATALOG, schema = EXPLICIT_SCHEMA) + private Long id; + @Basic + private String basic; + } + + @Entity(name = EntityWithDefaultQualifiersWithSeqHiLoGenerator.NAME) + public static class EntityWithDefaultQualifiersWithSeqHiLoGenerator { + public static final String NAME = "EntityWithDefaultQualifiersWithSeqHiLoGenerator"; + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = NAME + "_generator") + @GenericGenerator(name = NAME + "_generator", strategy = "org.hibernate.id.SequenceHiLoGenerator", parameters = { + @Parameter(name = "sequence", value = NAME + "_seq"), + @Parameter(name = "max_lo", value = "5") + }) + private Long id; + @Basic + private String basic; + } + + @Entity(name = EntityWithExplicitQualifiersWithSeqHiLoGenerator.NAME) + @Table(catalog = EXPLICIT_CATALOG, schema = EXPLICIT_SCHEMA) + public static class EntityWithExplicitQualifiersWithSeqHiLoGenerator { + public static final String NAME = "EntityWithExplicitQualifiersWithSeqHiLoGenerator"; + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = NAME + "_generator") + @GenericGenerator(name = NAME + "_generator", strategy = "org.hibernate.id.SequenceHiLoGenerator", parameters = { + @Parameter(name = "sequence", value = NAME + "_seq"), + @Parameter(name = "max_lo", value = "5"), + @Parameter(name = "catalog", value = EXPLICIT_CATALOG), + @Parameter(name = "schema", value = EXPLICIT_SCHEMA) + }) + private Long id; + @Basic + private String basic; + } + + @Entity(name = EntityWithDefaultQualifiersWithIncrementGenerator.NAME) + public static class EntityWithDefaultQualifiersWithIncrementGenerator { + public static final String NAME = "EntityWithDefaultQualifiersWithIncrementGenerator"; + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = NAME + "_generator") + @GenericGenerator(name = NAME + "_generator", strategy = "increment") + private Long id; + @Basic + private String basic; + } + + @Entity(name = EntityWithExplicitQualifiersWithIncrementGenerator.NAME) + @Table(catalog = EXPLICIT_CATALOG, schema = EXPLICIT_SCHEMA) + public static class EntityWithExplicitQualifiersWithIncrementGenerator { + public static final String NAME = "EntityWithExplicitQualifiersWithIncrementGenerator"; + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = NAME + "_generator") + @GenericGenerator(name = NAME + "_generator", strategy = "increment", parameters = { + @Parameter(name = "catalog", value = EXPLICIT_CATALOG), + @Parameter(name = "schema", value = EXPLICIT_SCHEMA) + }) + private Long id; + @Basic + private String basic; + } + + @Entity(name = EntityWithDefaultQualifiersWithSequenceIdentityGenerator.NAME) + public static class EntityWithDefaultQualifiersWithSequenceIdentityGenerator { + public static final String NAME = "EntityWithDefaultQualifiersWithSequenceIdentityGenerator"; + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = NAME + "_generator") + @GenericGenerator(name = NAME + "_generator", strategy = "sequence-identity", parameters = { + @Parameter(name = "sequence", value = NAME + "_seq") + }) + private Long id; + @Basic + private String basic; + } + + @Entity(name = EntityWithExplicitQualifiersWithSequenceIdentityGenerator.NAME) + @Table(catalog = EXPLICIT_CATALOG, schema = EXPLICIT_SCHEMA) + public static class EntityWithExplicitQualifiersWithSequenceIdentityGenerator { + public static final String NAME = "EntityWithExplicitQualifiersWithSequenceIdentityGenerator"; + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = NAME + "_generator") + @GenericGenerator(name = NAME + "_generator", strategy = "sequence-identity", parameters = { + @Parameter(name = "sequence", value = NAME + "_seq"), + @Parameter(name = "catalog", value = EXPLICIT_CATALOG), + @Parameter(name = "schema", value = EXPLICIT_SCHEMA) + }) + private Long id; + @Basic + private String basic; + } + + @Entity(name = EntityWithDefaultQualifiersWithEnhancedSequenceGenerator.NAME) + public static class EntityWithDefaultQualifiersWithEnhancedSequenceGenerator { + public static final String NAME = "EntityWithDefaultQualifiersWithEnhancedSequenceGenerator"; + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = NAME + "_generator") + @GenericGenerator(name = NAME + "_generator", strategy = "enhanced-sequence", parameters = { + @Parameter(name = "sequence_name", value = NAME + "_seq") + }) + private Long id; + @Basic + private String basic; + } + + @Entity(name = EntityWithExplicitQualifiersWithEnhancedSequenceGenerator.NAME) + @Table(catalog = EXPLICIT_CATALOG, schema = EXPLICIT_SCHEMA) + public static class EntityWithExplicitQualifiersWithEnhancedSequenceGenerator { + public static final String NAME = "EntityWithExplicitQualifiersWithEnhancedSequenceGenerator"; + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = NAME + "_generator") + @GenericGenerator(name = NAME + "_generator", strategy = "enhanced-sequence", parameters = { + @Parameter(name = "sequence_name", value = NAME + "_seq"), + @Parameter(name = "catalog", value = EXPLICIT_CATALOG), + @Parameter(name = "schema", value = EXPLICIT_SCHEMA) + }) + private Long id; + @Basic + private String basic; + } + + + @Entity(name = EntityWithDefaultQualifiersWithLegacySequenceGenerator.NAME) + public static class EntityWithDefaultQualifiersWithLegacySequenceGenerator { + public static final String NAME = "EntityWithDefaultQualifiersWithLegacySequenceGenerator"; + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = NAME + "_generator") + @GenericGenerator(name = NAME + "_generator", strategy = "org.hibernate.id.SequenceGenerator", parameters = { + @Parameter(name = "sequence", value = NAME + "_seq") + }) + private Long id; + @Basic + private String basic; + } + + @Entity(name = EntityWithExplicitQualifiersWithLegacySequenceGenerator.NAME) + @Table(catalog = EXPLICIT_CATALOG, schema = EXPLICIT_SCHEMA) + public static class EntityWithExplicitQualifiersWithLegacySequenceGenerator { + public static final String NAME = "EntityWithExplicitQualifiersWithLegacySequenceGenerator"; + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = NAME + "_generator") + @GenericGenerator(name = NAME + "_generator", strategy = "org.hibernate.id.SequenceGenerator", parameters = { + @Parameter(name = "sequence", value = NAME + "_seq"), + @Parameter(name = "catalog", value = EXPLICIT_CATALOG), + @Parameter(name = "schema", value = EXPLICIT_SCHEMA) + }) + private Long id; + @Basic + private String basic; + } + +} 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 05a50eb050cc..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; @@ -123,7 +127,7 @@ private static class ItemField implements Item { @ElementCollection( fetch = FetchType.EAGER ) @MapKeyColumn( name = "NAME" ) @Lob - @Column( name = "VALUE", length = 65535 ) + @Column( name = "PARAM_VAL", length = 65535 ) private Map parameters = new HashMap<>(); @Override @@ -153,7 +157,7 @@ public void setName(String name) { @ElementCollection( fetch = FetchType.EAGER ) @MapKeyColumn( name = "NAME" ) @Lob - @Column( name = "VALUE", length = 65535 ) + @Column( name = "PARAM_VAL", length = 65535 ) @Override public Map getParameters() { return parameterMap; 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/lazy/proxy/RoleEntity.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/RoleEntity.java index 18ae96adb2e3..bed0e11d9eb2 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/RoleEntity.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/RoleEntity.java @@ -16,6 +16,7 @@ import java.io.Serializable; import javax.persistence.Basic; +import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.Inheritance; @@ -34,6 +35,7 @@ public class RoleEntity extends ModelEntity implements Serializable { @Basic + @Column(name = "val") Short value; @ManyToOne(fetch = FetchType.LAZY) diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/SpecializedEntity.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/SpecializedEntity.java index 875cd594b29a..5cf380eeead5 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/SpecializedEntity.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/SpecializedEntity.java @@ -45,7 +45,7 @@ public void setId(Long id) { this.id = id; } - @Column(name="Value") + @Column(name="TheValue") String value; @ManyToOne(fetch=FetchType.LAZY) 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/RefreshUpdatedDataTest.java b/hibernate-core/src/test/java/org/hibernate/test/cache/RefreshUpdatedDataTest.java index 379cbdb508d0..ff5bb0ceb854 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/cache/RefreshUpdatedDataTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/cache/RefreshUpdatedDataTest.java @@ -49,7 +49,7 @@ protected void configure(Configuration cfg) { super.configure( cfg ); Properties properties = Environment.getProperties(); if ( H2Dialect.class.getName().equals( properties.get( Environment.DIALECT ) ) ) { - cfg.setProperty( Environment.URL, "jdbc:h2:mem:db-mvcc;MVCC=true" ); + cfg.setProperty( Environment.URL, "jdbc:h2:mem:db-mvcc" ); } cfg.setProperty( Environment.CACHE_REGION_PREFIX, "" ); cfg.setProperty( Environment.GENERATE_STATISTICS, "true" ); 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/cfg/WrongCircularityDetectionTest.java b/hibernate-core/src/test/java/org/hibernate/test/cfg/WrongCircularityDetectionTest.java index 4f0d3ca239fc..3c1bf53924ca 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/cfg/WrongCircularityDetectionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/cfg/WrongCircularityDetectionTest.java @@ -7,6 +7,7 @@ package org.hibernate.test.cfg; import javax.persistence.Basic; +import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Inheritance; @@ -76,6 +77,7 @@ public void setId(String id) { @Entity @Table(schema = "schema2", name = "entity") public static class Entity2 extends Entity1 { + @Column(name = "val") private String value; @Basic 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/comments/TestEntity.java b/hibernate-core/src/test/java/org/hibernate/test/comments/TestEntity.java index 7c425becc49e..a581d0a0e980 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/comments/TestEntity.java +++ b/hibernate-core/src/test/java/org/hibernate/test/comments/TestEntity.java @@ -6,6 +6,7 @@ */ package org.hibernate.test.comments; +import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; @@ -16,7 +17,7 @@ public class TestEntity { @Id private String id; - + @Column(name = "val") private String value; public TestEntity() { diff --git a/hibernate-core/src/test/java/org/hibernate/test/comments/TestEntity2.java b/hibernate-core/src/test/java/org/hibernate/test/comments/TestEntity2.java index 58b626df6039..d8a5fa9cb331 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/comments/TestEntity2.java +++ b/hibernate-core/src/test/java/org/hibernate/test/comments/TestEntity2.java @@ -6,6 +6,7 @@ */ package org.hibernate.test.comments; +import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; @@ -16,7 +17,7 @@ public class TestEntity2 { @Id private String id; - + @Column(name = "val") private String value; public String getId() { 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/constraint/ForeignKeyNoConstraintTest.java b/hibernate-core/src/test/java/org/hibernate/test/constraint/ForeignKeyNoConstraintTest.java index 408c4c0de657..2bf062002528 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/constraint/ForeignKeyNoConstraintTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/constraint/ForeignKeyNoConstraintTest.java @@ -6,6 +6,7 @@ */ package org.hibernate.test.constraint; +import javax.persistence.Column; import javax.persistence.ConstraintMode; import javax.persistence.Entity; import javax.persistence.FetchType; @@ -94,6 +95,7 @@ public void setVehicleNumber(VehicleNumber vehicleNumber) { public static class VehicleNumber { @Id private Integer id; + @Column(name = "val") private String value; public Integer getId() { diff --git a/hibernate-core/src/test/java/org/hibernate/test/converter/elementCollection/ElementCollectionTests.java b/hibernate-core/src/test/java/org/hibernate/test/converter/elementCollection/ElementCollectionTests.java index ce72adae151b..31ed96642fcb 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/converter/elementCollection/ElementCollectionTests.java +++ b/hibernate-core/src/test/java/org/hibernate/test/converter/elementCollection/ElementCollectionTests.java @@ -146,7 +146,7 @@ public static class TheEntity { @Convert( converter = ValueTypeConverter.class ) @ElementCollection(fetch = FetchType.LAZY) @CollectionTable(name = "entity_set", joinColumns = @JoinColumn(name = "entity_id", nullable = false)) - @Column(name = "value", nullable = false) + @Column(name = "val", nullable = false) public Set set = new HashSet(); /** @@ -159,7 +159,7 @@ public static class TheEntity { @ElementCollection(fetch = FetchType.LAZY) @CollectionTable(name = "entity_map", joinColumns = @JoinColumn(name = "entity_id", nullable = false)) @MapKeyColumn(name = "map_key", nullable = false) - @Column(name = "value", nullable = false) + @Column(name = "val", nullable = false) public Map map = new HashMap(); public TheEntity() { 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/criteria/Enrolment.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/criteria/Enrolment.hbm.xml index e5dbcc482ae1..d6b2cb787028 100755 --- a/hibernate-core/src/test/java/org/hibernate/test/criteria/Enrolment.hbm.xml +++ b/hibernate-core/src/test/java/org/hibernate/test/criteria/Enrolment.hbm.xml @@ -29,7 +29,7 @@ - + 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/fetchprofiles/EntityLoadedInTwoPhaseLoadTest.java b/hibernate-core/src/test/java/org/hibernate/test/fetchprofiles/EntityLoadedInTwoPhaseLoadTest.java index 65d44d752f14..686e8a107718 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/fetchprofiles/EntityLoadedInTwoPhaseLoadTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/fetchprofiles/EntityLoadedInTwoPhaseLoadTest.java @@ -90,7 +90,7 @@ public static class Finish { @GeneratedValue private long id; - @Column(name = "value", nullable = false) + @Column(name = "val", nullable = false) private String value; public Finish() { 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/hbm/uk/UniqueDelegateTest.java b/hibernate-core/src/test/java/org/hibernate/test/hbm/uk/UniqueDelegateTest.java index c2bd6ba76c41..488bf875adf3 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/hbm/uk/UniqueDelegateTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/hbm/uk/UniqueDelegateTest.java @@ -8,6 +8,7 @@ import org.hibernate.boot.Metadata; import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.boot.registry.StandardServiceRegistry; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.cfg.AvailableSettings; @@ -103,29 +104,33 @@ public MyUniqueDelegate(Dialect dialect) { } @Override - public String getColumnDefinitionUniquenessFragment(Column column) { + public String getColumnDefinitionUniquenessFragment(Column column, + SqlStringGenerationContext context) { getColumnDefinitionUniquenessFragmentCallCount++; - return super.getColumnDefinitionUniquenessFragment( column ); + return super.getColumnDefinitionUniquenessFragment( column, context ); } @Override - public String getTableCreationUniqueConstraintsFragment(Table table) { + public String getTableCreationUniqueConstraintsFragment(Table table, + SqlStringGenerationContext context) { getTableCreationUniqueConstraintsFragmentCallCount++; - return super.getTableCreationUniqueConstraintsFragment( table ); + return super.getTableCreationUniqueConstraintsFragment( table, context ); } @Override public String getAlterTableToAddUniqueKeyCommand( - UniqueKey uniqueKey, Metadata metadata) { + UniqueKey uniqueKey, Metadata metadata, + SqlStringGenerationContext context) { getAlterTableToAddUniqueKeyCommandCallCount++; - return super.getAlterTableToAddUniqueKeyCommand( uniqueKey, metadata ); + return super.getAlterTableToAddUniqueKeyCommand( uniqueKey, metadata, context ); } @Override public String getAlterTableToDropUniqueKeyCommand( - UniqueKey uniqueKey, Metadata metadata) { + UniqueKey uniqueKey, Metadata metadata, + SqlStringGenerationContext context) { getAlterTableToDropUniqueKeyCommandCallCount++; - return super.getAlterTableToDropUniqueKeyCommand( uniqueKey, metadata ); + return super.getAlterTableToDropUniqueKeyCommand( uniqueKey, metadata, context ); } } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/CollectionMapWithComponentValueTest.java b/hibernate-core/src/test/java/org/hibernate/test/hql/CollectionMapWithComponentValueTest.java index 2bb301639f1b..d3a0b8817c00 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/hql/CollectionMapWithComponentValueTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/hql/CollectionMapWithComponentValueTest.java @@ -6,6 +6,7 @@ */ package org.hibernate.test.hql; +import javax.persistence.Column; import javax.persistence.ElementCollection; import javax.persistence.Embeddable; import javax.persistence.Entity; @@ -243,6 +244,7 @@ public KeyValue(String name) { @Embeddable public static class EmbeddableValue { + @Column(name = "val") Integer value; EmbeddableValue() { diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/DeleteAllWithTablePerClassAndDefaultSchemaTest.java b/hibernate-core/src/test/java/org/hibernate/test/hql/DeleteAllWithTablePerClassAndDefaultSchemaTest.java new file mode 100644 index 000000000000..43d6fd24cb64 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/hql/DeleteAllWithTablePerClassAndDefaultSchemaTest.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.test.hql; + +import static org.assertj.core.api.Assertions.assertThat; + +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; + +@TestForIssue(jiraKey = "HHH-15022") +@RequiresDialect(PostgreSQL81Dialect.class) +public class DeleteAllWithTablePerClassAndDefaultSchemaTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { SuperEntity.class, SubEntity1.class, SubEntity2.class }; + } + + @Override + protected void configure(Configuration configuration) { + configuration.setProperty( AvailableSettings.DEFAULT_SCHEMA, "public" ); + } + + @Before + public void setUp() { + inTransaction( session -> { + SuperEntity entity1 = new SubEntity1( 1L, "super1", "sub1" ); + SuperEntity entity2 = new SubEntity2( 2L, "super2", "sub2" ); + session.persist( entity1 ); + session.persist( entity2 ); + } ); + } + + @Test + public void testDeleteAll() { + inTransaction( session -> { + assertThat( session.createQuery( "select count(*) from superent" ).uniqueResult() ) + .isEqualTo( 2L ); + } ); + inTransaction( session -> { + session.createQuery( "delete from subent1" ).executeUpdate(); + } ); + inTransaction( session -> { + assertThat( session.createQuery( "select count(*) from superent" ).uniqueResult() ) + .isEqualTo( 1L ); + } ); + } + + @Entity(name = "superent") + @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) + public abstract static class SuperEntity { + @Id + private Long id; + + private String superProperty; + + public SuperEntity() { + } + + public SuperEntity(Long id, String superProperty) { + this.id = id; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + } + + @Entity(name = "subent1") + public static class SubEntity1 extends SuperEntity { + private String subProperty1; + + public SubEntity1() { + } + + public SubEntity1(Long id, String superProperty, String subProperty) { + super( id, superProperty ); + this.subProperty1 = subProperty; + } + + public String getSubProperty1() { + return subProperty1; + } + + public void setSubProperty1(String subProperty1) { + this.subProperty1 = subProperty1; + } + } + + @Entity(name = "subent2") + public static class SubEntity2 extends SuperEntity { + private String subProperty2; + + public SubEntity2() { + } + + public SubEntity2(Long id, String superProperty, String subProperty) { + super( id, superProperty ); + this.subProperty2 = subProperty; + } + + public String getSubProperty2() { + return subProperty2; + } + + public void setSubProperty2(String subProperty2) { + this.subProperty2 = subProperty2; + } + } + +} 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/QueryParametersValidationArrayTest.java b/hibernate-core/src/test/java/org/hibernate/test/hql/QueryParametersValidationArrayTest.java index b8457c56b47e..52b87f27f584 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/hql/QueryParametersValidationArrayTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/hql/QueryParametersValidationArrayTest.java @@ -32,7 +32,9 @@ import org.hibernate.type.descriptor.sql.BasicExtractor; import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; +import org.hibernate.testing.DialectChecks; import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.TestForIssue; import org.junit.Test; @@ -45,6 +47,7 @@ */ @TestForIssue( jiraKey = "HHH-12292") @RequiresDialect(H2Dialect.class) +@RequiresDialectFeature(value = DialectChecks.NotH2Version2.class, comment = "Array support was changed to now be typed") public class QueryParametersValidationArrayTest extends BaseEntityManagerFunctionalTestCase { @Override 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 8fe9115577a6..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 @@ -6,11 +6,15 @@ */ package org.hibernate.test.hql; +import java.lang.reflect.Field; +import java.util.Map; +import java.util.stream.IntStream; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; import org.hibernate.hql.internal.QuerySplitter; +import org.hibernate.metamodel.internal.MetamodelImpl; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; @@ -47,6 +51,34 @@ public void testQueryWithEntityNameAsStringLiteral2() { ); } + @Test + @TestForIssue(jiraKey = "HHH-14948") + public void testMemoryConsumptionOfFailedImportsCache() throws NoSuchFieldException, IllegalAccessException { + + IntStream.range( 0, 1001 ) + .forEach( i -> QuerySplitter.concreteQueries( + "from Employee e join e.company" + i, + sessionFactory() + ) ); + + Map validImports = extractMapFromMetamodel("knownValidImports"); + Map invalidImports = extractMapFromMetamodel("knownInvalidImports"); + + 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( 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 @TestForIssue(jiraKey = "HHH-7973" ) public void testQueryWithEntityNameAsStringLiteralAndEscapeQuoteChar() { 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/fetchAndJoin/Child.java b/hibernate-core/src/test/java/org/hibernate/test/hql/fetchAndJoin/Child.java index 00131877d7e9..9366bc709d9c 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/hql/fetchAndJoin/Child.java +++ b/hibernate-core/src/test/java/org/hibernate/test/hql/fetchAndJoin/Child.java @@ -26,6 +26,7 @@ import java.util.HashSet; import java.util.Set; import javax.persistence.CascadeType; +import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; @@ -52,6 +53,7 @@ public Child(String value) { this.value = value; } + @Column(name = "val") private String value; public long getId() { diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/fetchAndJoin/Entity1.java b/hibernate-core/src/test/java/org/hibernate/test/hql/fetchAndJoin/Entity1.java index 2e9778484b91..7a9df225242f 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/hql/fetchAndJoin/Entity1.java +++ b/hibernate-core/src/test/java/org/hibernate/test/hql/fetchAndJoin/Entity1.java @@ -23,6 +23,7 @@ */ package org.hibernate.test.hql.fetchAndJoin; +import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; @@ -41,6 +42,7 @@ public class Entity1 { @JoinColumn(name="entity2_id", nullable = false) private Entity2 entity2; + @Column(name = "val") private String value; public long getId() { diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/fetchAndJoin/Entity2.java b/hibernate-core/src/test/java/org/hibernate/test/hql/fetchAndJoin/Entity2.java index 3edf588156e5..cde9b7f7f1df 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/hql/fetchAndJoin/Entity2.java +++ b/hibernate-core/src/test/java/org/hibernate/test/hql/fetchAndJoin/Entity2.java @@ -23,6 +23,7 @@ */ package org.hibernate.test.hql.fetchAndJoin; +import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; @@ -42,6 +43,7 @@ public class Entity2 { @JoinColumn(name="entity3_id") private Entity3 entity3; + @Column(name = "val") private String value; public long getId() { diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/fetchAndJoin/Entity3.java b/hibernate-core/src/test/java/org/hibernate/test/hql/fetchAndJoin/Entity3.java index 54234438a199..ac4cc006dbab 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/hql/fetchAndJoin/Entity3.java +++ b/hibernate-core/src/test/java/org/hibernate/test/hql/fetchAndJoin/Entity3.java @@ -23,6 +23,7 @@ */ package org.hibernate.test.hql.fetchAndJoin; +import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; @@ -35,6 +36,7 @@ public class Entity3 { @GeneratedValue private long id; + @Column(name = "val") private String value; public long getId() { diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/fetchAndJoin/GrandChild.java b/hibernate-core/src/test/java/org/hibernate/test/hql/fetchAndJoin/GrandChild.java index 6a0ab7a3b741..88589b6f5463 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/hql/fetchAndJoin/GrandChild.java +++ b/hibernate-core/src/test/java/org/hibernate/test/hql/fetchAndJoin/GrandChild.java @@ -23,6 +23,7 @@ */ package org.hibernate.test.hql.fetchAndJoin; +import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; @@ -35,6 +36,7 @@ public class GrandChild { @GeneratedValue private long id; + @Column(name = "val") private String value; public GrandChild() { diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/fetchAndJoin/Parent.java b/hibernate-core/src/test/java/org/hibernate/test/hql/fetchAndJoin/Parent.java index d7845fdc4695..5f8bf40fe02e 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/hql/fetchAndJoin/Parent.java +++ b/hibernate-core/src/test/java/org/hibernate/test/hql/fetchAndJoin/Parent.java @@ -26,6 +26,7 @@ import java.util.HashSet; import java.util.Set; import javax.persistence.CascadeType; +import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; @@ -45,6 +46,7 @@ public class Parent { @JoinColumn private Set children = new HashSet(); + @Column(name = "val") private String value; public Parent() { 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/auto/NewGeneratorsTests.java b/hibernate-core/src/test/java/org/hibernate/test/idgen/enhanced/auto/NewGeneratorsTests.java index 5d73e16074e3..88f1965e05cb 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/idgen/enhanced/auto/NewGeneratorsTests.java +++ b/hibernate-core/src/test/java/org/hibernate/test/idgen/enhanced/auto/NewGeneratorsTests.java @@ -70,9 +70,9 @@ public void testAutoDefaults() { final DatabaseStructure databaseStructure = generator.getDatabaseStructure(); // HHH-14491 - what we want to happen - assertThat( databaseStructure.getName(), is( "Entity1_SEQ" ) ); + assertThat( databaseStructure.getPhysicalName().render(), is( "Entity1_SEQ" ) ); // or this depending on the discussion (Jira) about using entity name v. table name as the base - assertThat( databaseStructure.getName(), is( "tbl_1_SEQ" ) ); + assertThat( databaseStructure.getPhysicalName().render(), is( "tbl_1_SEQ" ) ); // HHH-14491 - this is what we want to have happen assertThat( databaseStructure.getIncrementSize(), is( 50 ) ); 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/BasicSequenceTest.java b/hibernate-core/src/test/java/org/hibernate/test/idgen/enhanced/sequence/BasicSequenceTest.java index ec1e00f8a778..e91c214c8432 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/idgen/enhanced/sequence/BasicSequenceTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/idgen/enhanced/sequence/BasicSequenceTest.java @@ -64,7 +64,8 @@ public void testSequencePerEntity() { EntityPersister persister = sessionFactory().getEntityPersister( overriddenEntityName ); assertClassAssignability( SequenceStyleGenerator.class, persister.getIdentifierGenerator().getClass() ); SequenceStyleGenerator generator = (SequenceStyleGenerator) persister.getIdentifierGenerator(); - assertEquals( overriddenEntityName + SequenceStyleGenerator.DEF_SEQUENCE_SUFFIX, generator.getDatabaseStructure().getName() ); + assertEquals( overriddenEntityName + SequenceStyleGenerator.DEF_SEQUENCE_SUFFIX, + generator.getDatabaseStructure().getPhysicalName().getObjectName().getText() ); Session s = openSession(); s.beginTransaction(); diff --git a/hibernate-core/src/test/java/org/hibernate/test/idgen/enhanced/sequence/HiLoSequenceMismatchStrategyTest.java b/hibernate-core/src/test/java/org/hibernate/test/idgen/enhanced/sequence/HiLoSequenceMismatchStrategyTest.java index 759392532538..f9e43a4355b3 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/idgen/enhanced/sequence/HiLoSequenceMismatchStrategyTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/idgen/enhanced/sequence/HiLoSequenceMismatchStrategyTest.java @@ -16,6 +16,7 @@ import org.hibernate.annotations.GenericGenerator; import org.hibernate.annotations.Parameter; +import org.hibernate.boot.model.relational.QualifiedName; import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.Configuration; import org.hibernate.cfg.Environment; @@ -93,8 +94,8 @@ public void testSequenceMismatchStrategyNotApplied() { SequenceStyleGenerator generator = (SequenceStyleGenerator) persister.getIdentifierGenerator(); assertClassAssignability( HiLoOptimizer.class, generator.getOptimizer().getClass() ); - String sequenceName = generator.getDatabaseStructure().getName(); - Assert.assertEquals( this.sequenceName, sequenceName ); + QualifiedName sequenceName = generator.getDatabaseStructure().getPhysicalName(); + Assert.assertEquals( this.sequenceName, sequenceName.render() ); int incrementSize = generator.getOptimizer().getIncrementSize(); Assert.assertNotEquals( 1, incrementSize ); 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/Db2GenerationTest.java b/hibernate-core/src/test/java/org/hibernate/test/idgen/enhanced/table/Db2GenerationTest.java index cafc49a11186..5a90b082a57b 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/idgen/enhanced/table/Db2GenerationTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/idgen/enhanced/table/Db2GenerationTest.java @@ -10,6 +10,9 @@ import org.hibernate.boot.Metadata; import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.model.relational.Database; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; +import org.hibernate.boot.model.relational.internal.SqlStringGenerationContextImpl; import org.hibernate.boot.registry.StandardServiceRegistry; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.cfg.AvailableSettings; @@ -52,8 +55,13 @@ public void testNewGeneratorTableCreationOnDb2() { assertEquals( 1, metadata.getDatabase().getDefaultNamespace().getTables().size() ); - final Table table = metadata.getDatabase().getDefaultNamespace().getTables().iterator().next(); - final String[] createCommands = new DB2Dialect().getTableExporter().getSqlCreateStrings( table, metadata ); + Database database = metadata.getDatabase(); + final Table table = database.getDefaultNamespace().getTables().iterator().next(); + SqlStringGenerationContext sqlStringGenerationContext = + SqlStringGenerationContextImpl.forTests( database.getJdbcEnvironment() ); + final String[] createCommands = new DB2Dialect().getTableExporter().getSqlCreateStrings( table, metadata, + sqlStringGenerationContext + ); assertContains( "sequence_name varchar(255) not null", createCommands[0] ); } finally { @@ -88,8 +96,13 @@ public void testLegacyGeneratorTableCreationOnDb2() { assertEquals( 1, metadata.getDatabase().getDefaultNamespace().getTables().size() ); - final Table table = metadata.getDatabase().getDefaultNamespace().getTables().iterator().next(); - final String[] createCommands = new DB2Dialect().getTableExporter().getSqlCreateStrings( table, metadata ); + Database database = metadata.getDatabase(); + final Table table = database.getDefaultNamespace().getTables().iterator().next(); + SqlStringGenerationContext sqlStringGenerationContext = + SqlStringGenerationContextImpl.forTests( database.getJdbcEnvironment() ); + final String[] createCommands = new DB2Dialect().getTableExporter().getSqlCreateStrings( table, metadata, + sqlStringGenerationContext + ); assertContains( "sequence_name varchar(255) not null", createCommands[0] ); } finally { 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/idgen/namescope/IdGeneratorNamesLocalScopeTest.java b/hibernate-core/src/test/java/org/hibernate/test/idgen/namescope/IdGeneratorNamesLocalScopeTest.java index b403c6ef317b..6fbe8d90be24 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/idgen/namescope/IdGeneratorNamesLocalScopeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/idgen/namescope/IdGeneratorNamesLocalScopeTest.java @@ -54,7 +54,7 @@ public void testNoSequenceGenratorNameClash() { name = "table-generator", table = "table_identifier_2", pkColumnName = "identifier", - valueColumnName = "value", + valueColumnName = "val", allocationSize = 5, initialValue = 1 ) @@ -73,7 +73,7 @@ public Long getId() { name = "table-generator", table = "table_identifier", pkColumnName = "identifier", - valueColumnName = "value", + valueColumnName = "val", allocationSize = 5, initialValue = 10 ) 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/join/OuterJoinTest.java b/hibernate-core/src/test/java/org/hibernate/test/join/OuterJoinTest.java index e4da3b85c889..f067e68e4658 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/join/OuterJoinTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/join/OuterJoinTest.java @@ -28,6 +28,7 @@ public static class BaseEntity { @Column(name = "key_id") Long key; + @Column(name = "val") String value; @ManyToOne(optional = false) @@ -102,6 +103,7 @@ public static class Association { @Column(name = "key_id") private Long key; + @Column(name = "val") private String value; public Association() { diff --git a/hibernate-core/src/test/java/org/hibernate/test/jpa/compliance/tck2_2/GeneratedValueTests.java b/hibernate-core/src/test/java/org/hibernate/test/jpa/compliance/tck2_2/GeneratedValueTests.java index 3fd7d89eb86b..de1d7458124e 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/jpa/compliance/tck2_2/GeneratedValueTests.java +++ b/hibernate-core/src/test/java/org/hibernate/test/jpa/compliance/tck2_2/GeneratedValueTests.java @@ -16,7 +16,10 @@ import org.hibernate.boot.Metadata; import org.hibernate.boot.MetadataSources; import org.hibernate.boot.model.naming.Identifier; +import org.hibernate.boot.model.relational.Database; import org.hibernate.boot.model.relational.Sequence; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; +import org.hibernate.boot.model.relational.internal.SqlStringGenerationContextImpl; import org.hibernate.boot.registry.StandardServiceRegistry; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.cfg.AvailableSettings; @@ -61,13 +64,13 @@ public void baseline() { null, (RootClass) entityMapping ); + generator.initialize( SqlStringGenerationContextImpl.forTests( bootModel.getDatabase().getJdbcEnvironment() ) ); final SequenceStyleGenerator sequenceStyleGenerator = assertTyping( SequenceStyleGenerator.class, generator ); - - assertThat( sequenceStyleGenerator.getDatabaseStructure().getName(), is( "my_real_db_sequence" ) ); + assertThat( sequenceStyleGenerator.getDatabaseStructure().getPhysicalName().render(), is( "my_real_db_sequence" ) ); // all the JPA defaults since they were not defined assertThat( sequenceStyleGenerator.getDatabaseStructure().getInitialValue(), is( 100 ) ); @@ -91,6 +94,7 @@ public void testImplicitSequenceGenerator() { null, (RootClass) entityMapping ); + generator.initialize( SqlStringGenerationContextImpl.forTests( bootModel.getDatabase().getJdbcEnvironment() ) ); final SequenceStyleGenerator sequenceStyleGenerator = assertTyping( SequenceStyleGenerator.class, @@ -99,7 +103,7 @@ public void testImplicitSequenceGenerator() { // PREFER_GENERATOR_NAME_AS_DEFAULT_SEQUENCE_NAME == false indicates that the legacy // default (hibernate_sequence) should be used - assertThat( sequenceStyleGenerator.getDatabaseStructure().getName(), is( "hibernate_sequence" ) ); + assertThat( sequenceStyleGenerator.getDatabaseStructure().getPhysicalName().render(), is( "hibernate_sequence" ) ); // the JPA defaults since they were not defined assertThat( sequenceStyleGenerator.getDatabaseStructure().getInitialValue(), is( 1 ) ); @@ -121,6 +125,7 @@ public void testImplicitSequenceGeneratorGeneratorName() { null, (RootClass) entityMapping ); + generator.initialize( SqlStringGenerationContextImpl.forTests( bootModel.getDatabase().getJdbcEnvironment() ) ); final SequenceStyleGenerator sequenceStyleGenerator = assertTyping( SequenceStyleGenerator.class, @@ -129,7 +134,7 @@ public void testImplicitSequenceGeneratorGeneratorName() { // PREFER_GENERATOR_NAME_AS_DEFAULT_SEQUENCE_NAME == true (the default) indicates that the generator-name // should be used as the default instead. - assertThat( sequenceStyleGenerator.getDatabaseStructure().getName(), is( "my_db_sequence" ) ); + assertThat( sequenceStyleGenerator.getDatabaseStructure().getPhysicalName().render(), is( "my_db_sequence" ) ); // the JPA defaults since they were not defined assertThat( sequenceStyleGenerator.getDatabaseStructure().getInitialValue(), is( 1 ) ); @@ -153,10 +158,11 @@ public void testExplicitSequenceGeneratorImplicitName() { null, (RootClass) entityMapping ); + generator.initialize( SqlStringGenerationContextImpl.forTests( bootModel.getDatabase().getJdbcEnvironment() ) ); final SequenceStyleGenerator sequenceStyleGenerator = assertTyping( SequenceStyleGenerator.class, generator ); // all the JPA defaults since they were not defined - assertThat( sequenceStyleGenerator.getDatabaseStructure().getName(), is( SequenceStyleGenerator.DEF_SEQUENCE_NAME ) ); + assertThat( sequenceStyleGenerator.getDatabaseStructure().getPhysicalName().render(), is( SequenceStyleGenerator.DEF_SEQUENCE_NAME ) ); assertThat( sequenceStyleGenerator.getDatabaseStructure().getInitialValue(), is( 100 ) ); assertThat( sequenceStyleGenerator.getDatabaseStructure().getIncrementSize(), is( 500 ) ); } @@ -177,23 +183,27 @@ public void testExplicitSequenceGeneratorImplicitNamePreferGeneratorName() { null, (RootClass) entityMapping ); + Database database = bootModel.getDatabase(); + SqlStringGenerationContext sqlStringGenerationContext = + SqlStringGenerationContextImpl.forTests( database.getJdbcEnvironment() ); + generator.initialize( sqlStringGenerationContext ); final SequenceStyleGenerator sequenceStyleGenerator = assertTyping( SequenceStyleGenerator.class, generator ); // all the JPA defaults since they were not defined - assertThat( sequenceStyleGenerator.getDatabaseStructure().getName(), is( "my_db_sequence" ) ); + assertThat( sequenceStyleGenerator.getDatabaseStructure().getPhysicalName().render(), is( "my_db_sequence" ) ); assertThat( sequenceStyleGenerator.getDatabaseStructure().getInitialValue(), is( 100 ) ); assertThat( sequenceStyleGenerator.getDatabaseStructure().getIncrementSize(), is( 500 ) ); - final Sequence sequence = bootModel.getDatabase() - .getDefaultNamespace() + final Sequence sequence = database.getDefaultNamespace() .locateSequence( Identifier.toIdentifier( "my_db_sequence" ) ); assertThat( sequence, notNullValue() ); final String[] sqlCreateStrings = new H2Dialect().getSequenceExporter().getSqlCreateStrings( sequence, - bootModel + bootModel, + sqlStringGenerationContext ); assertThat( sqlCreateStrings.length, is( 1 ) ); final String cmd = sqlCreateStrings[0].toLowerCase(); @@ -215,6 +225,7 @@ public void testImplicitTableGenerator() { null, (RootClass) entityMapping ); + generator.initialize( SqlStringGenerationContextImpl.forTests( bootModel.getDatabase().getJdbcEnvironment() ) ); final TableGenerator tableGenerator = assertTyping( TableGenerator.class, generator ); @@ -240,6 +251,7 @@ public void testExplicitTableGeneratorImplicitName() { null, (RootClass) entityMapping ); + generator.initialize( SqlStringGenerationContextImpl.forTests( bootModel.getDatabase().getJdbcEnvironment() ) ); final TableGenerator tableGenerator = assertTyping( TableGenerator.class, generator ); @@ -265,6 +277,7 @@ public void testExplicitTableGenerator() { null, (RootClass) entityMapping ); + generator.initialize( SqlStringGenerationContextImpl.forTests( bootModel.getDatabase().getJdbcEnvironment() ) ); final TableGenerator tableGenerator = assertTyping( TableGenerator.class, generator ); @@ -292,6 +305,7 @@ public void testExplicitIncrementGenerator() { null, (RootClass) entityMapping ); + generator.initialize( SqlStringGenerationContextImpl.forTests( bootModel.getDatabase().getJdbcEnvironment() ) ); assertTyping( IncrementGenerator.class, generator ); } @@ -311,6 +325,7 @@ public void testImplicitIncrementGenerator() { null, (RootClass) entityMapping ); + generator.initialize( SqlStringGenerationContextImpl.forTests( bootModel.getDatabase().getJdbcEnvironment() ) ); assertTyping( IncrementGenerator.class, generator ); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/jpa/compliance/tck2_2/TableGeneratorVisibilityTest.java b/hibernate-core/src/test/java/org/hibernate/test/jpa/compliance/tck2_2/TableGeneratorVisibilityTest.java index 35ef8ecaa3fd..b028ef498e7d 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/jpa/compliance/tck2_2/TableGeneratorVisibilityTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/jpa/compliance/tck2_2/TableGeneratorVisibilityTest.java @@ -75,7 +75,7 @@ public static class TestEntity2 { name = "table-generator", table = "table_identifier", pkColumnName = "identifier", - valueColumnName = "value", + valueColumnName = "val", allocationSize = 5 ) public static class TestEntity3 { @@ -84,7 +84,7 @@ public static class TestEntity3 { name = "table-generator-2", table = "table_identifier_2", pkColumnName = "identifier", - valueColumnName = "value", + valueColumnName = "val", allocationSize = 5 ) @GeneratedValue(strategy = GenerationType.TABLE, generator = "table-generator") 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/legacy/FooBarTest.java b/hibernate-core/src/test/java/org/hibernate/test/legacy/FooBarTest.java index 481edfb5c1f6..8a2a4fb787a3 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/legacy/FooBarTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/legacy/FooBarTest.java @@ -71,7 +71,8 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -@RequiresDialectFeature(DialectChecks.SupportsNoColumnInsert.class) +//@RequiresDialectFeature(DialectChecks.SupportsNoColumnInsert.class) +@RequiresDialectFeature(value = {DialectChecks.SupportsNoColumnInsert.class, DialectChecks.NotH2Version2.class}, comment = "See https://github.com/h2database/h2database/issues/3385") public class FooBarTest extends LegacyTestCase { @Override diff --git a/hibernate-core/src/test/java/org/hibernate/test/legacy/FumTest.java b/hibernate-core/src/test/java/org/hibernate/test/legacy/FumTest.java index 8f6032765cab..883a1e1097a1 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/legacy/FumTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/legacy/FumTest.java @@ -61,7 +61,8 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; -@RequiresDialectFeature(DialectChecks.SupportsNoColumnInsert.class) +//@RequiresDialectFeature(DialectChecks.SupportsNoColumnInsert.class) +@RequiresDialectFeature(value = {DialectChecks.SupportsNoColumnInsert.class, DialectChecks.NotH2Version2.class}, comment = "See https://github.com/h2database/h2database/issues/3385") public class FumTest extends LegacyTestCase { private static short fumKeyShort = 1; diff --git a/hibernate-core/src/test/java/org/hibernate/test/legacy/IJTest.java b/hibernate-core/src/test/java/org/hibernate/test/legacy/IJTest.java index d3a25cccff64..178f35c99785 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/legacy/IJTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/legacy/IJTest.java @@ -15,6 +15,8 @@ import org.hibernate.Session; import org.hibernate.dialect.HSQLDialect; +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; import org.junit.Test; import static org.junit.Assert.assertTrue; @@ -22,6 +24,7 @@ /** * @author Gavin King */ +@RequiresDialectFeature(value = { DialectChecks.NotH2Version2.class}, comment = "See https://github.com/h2database/h2database/issues/3385") public class IJTest extends LegacyTestCase { @Override public String[] getMappings() { diff --git a/hibernate-core/src/test/java/org/hibernate/test/legacy/ParentChildTest.java b/hibernate-core/src/test/java/org/hibernate/test/legacy/ParentChildTest.java index 09229198a6ed..02b36deefe4f 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/legacy/ParentChildTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/legacy/ParentChildTest.java @@ -1184,6 +1184,7 @@ public void testLocking() throws Exception { } @Test + @RequiresDialectFeature(value = { DialectChecks.NotH2Version2.class}, comment = "See https://github.com/h2database/h2database/issues/3385") public void testObjectType() throws Exception { Session s = openSession(); s.beginTransaction(); 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/lob/PostgreSqlLobStringTest.java b/hibernate-core/src/test/java/org/hibernate/test/lob/PostgreSqlLobStringTest.java index 1ba2ce15d1b4..c521dbdcf269 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/lob/PostgreSqlLobStringTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/lob/PostgreSqlLobStringTest.java @@ -65,9 +65,9 @@ protected void prepareTest() " (?, ?, ?, -1)" )) { int index = 1; - statement.setString(index++, value1); - statement.setString(index++, value2); - statement.setString(index++, value3); + statement.setClob( index++, session.getLobHelper().createClob( value1 ) ); + statement.setClob( index++, session.getLobHelper().createClob( value2 ) ); + statement.setClob( index++, session.getLobHelper().createClob( value3 ) ); assertEquals( 1, statement.executeUpdate() ); } @@ -77,57 +77,63 @@ protected void prepareTest() @Test public void testBadClobDataSavedAsStringFails() { - try { - doInHibernate( this::sessionFactory, session -> { - final Query query = session.createQuery( "from TestEntity" ); + doInHibernate( this::sessionFactory, session -> { + final Query query = session.createQuery( "from TestEntity" ); - final List results = query.list(); + final List results = query.list(); - fail("Exception thrown expected"); - } ); - } - catch (Exception e) { - Exception rootException = (Exception) ExceptionUtil.rootCause( e ); - assertTrue( rootException.getMessage().startsWith( "Bad value for type long" ) ); - } + assertThat( results.size(), is( 1 ) ); + + final TestEntity testEntity = results.get( 0 ); + assertThat( testEntity.getFirstLobField(), is( value1 ) ); + assertThat( testEntity.getSecondLobField(), is( value2 ) ); + final Clob clobField = testEntity.getClobField(); + try { + + assertThat( clobField.getSubString( 1, (int) clobField.length() ), is( value3 ) ); + } + catch (SQLException e) { + fail( e.getMessage() ); + } + } ); } @Test public void testBadClobDataSavedAsStringworksAfterUpdate() { doInHibernate( this::sessionFactory, session -> { - session.doWork( connection -> { - try(Statement statement = connection.createStatement()) { - statement.executeUpdate( - "update test_entity\n" + - "set \n" + - " clobfield = lo_from_bytea(0, cast(clobfield as bytea)),\n" + - " firstlobfield = lo_from_bytea(0, cast(firstlobfield as bytea)),\n" + - " secondlobfield = lo_from_bytea(0, cast(secondlobfield as bytea))" - ); - } - } ); - } ); + session.doWork( connection -> { + try (Statement statement = connection.createStatement()) { + statement.executeUpdate( + "update test_entity\n" + + "set \n" + + " clobfield = lo_from_bytea(0, lo_get(clobfield)),\n" + + " firstlobfield = lo_from_bytea(0, lo_get(firstlobfield)),\n" + + " secondlobfield = lo_from_bytea(0, lo_get(secondlobfield))" + ); + } + } ); + } ); doInHibernate( this::sessionFactory, session -> { - final Query query = session.createQuery( "from TestEntity" ); + final Query query = session.createQuery( "from TestEntity" ); - final List results = query.list(); + final List results = query.list(); - assertThat( results.size(), is( 1 ) ); + assertThat( results.size(), is( 1 ) ); - final TestEntity testEntity = results.get( 0 ); - assertThat( testEntity.getFirstLobField(), is( value1 ) ); - assertThat( testEntity.getSecondLobField(), is( value2 ) ); - final Clob clobField = testEntity.getClobField(); - try { + final TestEntity testEntity = results.get( 0 ); + assertThat( testEntity.getFirstLobField(), is( value1 ) ); + assertThat( testEntity.getSecondLobField(), is( value2 ) ); + final Clob clobField = testEntity.getClobField(); + try { - assertThat( clobField.getSubString( 1, (int) clobField.length() ), is( value3 ) ); - } - catch (SQLException e) { - fail( e.getMessage() ); - } - } ); + assertThat( clobField.getSubString( 1, (int) clobField.length() ), is( value3 ) ); + } + catch (SQLException e) { + fail( e.getMessage() ); + } + } ); } @Entity(name = "TestEntity") diff --git a/hibernate-core/src/test/java/org/hibernate/test/locking/A.java b/hibernate-core/src/test/java/org/hibernate/test/locking/A.java index 243c4982b4b2..8c446b9707bd 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/locking/A.java +++ b/hibernate-core/src/test/java/org/hibernate/test/locking/A.java @@ -21,6 +21,7 @@ @Table( name = "T_LOCK_A" ) public class A { private Long id; + @Column(name = "val") private String value; public A() { 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/math/Math.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/math/Math.hbm.xml index bd51722ec078..4140c052a07b 100755 --- a/hibernate-core/src/test/java/org/hibernate/test/math/Math.hbm.xml +++ b/hibernate-core/src/test/java/org/hibernate/test/math/Math.hbm.xml @@ -20,7 +20,7 @@ - + diff --git a/hibernate-core/src/test/java/org/hibernate/test/mixed/MixedTest.java b/hibernate-core/src/test/java/org/hibernate/test/mixed/MixedTest.java index 51f5d633dc2f..ade9e2b27371 100755 --- a/hibernate-core/src/test/java/org/hibernate/test/mixed/MixedTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/mixed/MixedTest.java @@ -10,6 +10,8 @@ import org.hibernate.Transaction; import org.hibernate.dialect.SybaseASE15Dialect; +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.SkipLog; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; @@ -22,6 +24,7 @@ * @author Gavin King */ @SkipForDialect( SybaseASE15Dialect.class ) +@RequiresDialectFeature(value = { DialectChecks.NotH2Version2.class}, comment = "See https://github.com/h2database/h2database/issues/3385") public class MixedTest extends BaseCoreFunctionalTestCase { @Override public String[] getMappings() { diff --git a/hibernate-core/src/test/java/org/hibernate/test/onetoone/cache/OneToOneCacheEnableSelectingTest.java b/hibernate-core/src/test/java/org/hibernate/test/onetoone/cache/OneToOneCacheEnableSelectingTest.java index 35b0aa031efe..d2796769d005 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/onetoone/cache/OneToOneCacheEnableSelectingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/onetoone/cache/OneToOneCacheEnableSelectingTest.java @@ -54,6 +54,7 @@ public void testFieldShouldNotBeNull() { Product product = s.find(Product.class, pid.get()); ProductConfig config = new ProductConfig(); config.setProduct(product); + product.setConfig( config ); s.persist(config); }); 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/optlock/Document.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/optlock/Document.hbm.xml index 00473d672d00..553c7a0b84b4 100755 --- a/hibernate-core/src/test/java/org/hibernate/test/optlock/Document.hbm.xml +++ b/hibernate-core/src/test/java/org/hibernate/test/optlock/Document.hbm.xml @@ -27,7 +27,7 @@ - + @@ -42,7 +42,7 @@ - + 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/schematools/TestExtraPhysicalTableTypes.java b/hibernate-core/src/test/java/org/hibernate/test/schematools/TestExtraPhysicalTableTypes.java index 7a5a6d0b637d..a44990c1da55 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/schematools/TestExtraPhysicalTableTypes.java +++ b/hibernate-core/src/test/java/org/hibernate/test/schematools/TestExtraPhysicalTableTypes.java @@ -11,13 +11,16 @@ import org.hibernate.boot.MetadataSources; import org.hibernate.boot.model.relational.Database; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; +import org.hibernate.boot.model.relational.internal.SqlStringGenerationContextImpl; import org.hibernate.boot.registry.StandardServiceRegistry; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.cfg.Environment; +import org.hibernate.dialect.Dialect; +import org.hibernate.dialect.H2Dialect; import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; import org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator; -import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.resource.transaction.spi.DdlTransactionIsolator; import org.hibernate.tool.schema.extract.internal.DatabaseInformationImpl; @@ -25,11 +28,11 @@ import org.hibernate.tool.schema.extract.internal.InformationExtractorJdbcDatabaseMetaDataImpl; import org.hibernate.tool.schema.extract.spi.DatabaseInformation; import org.hibernate.tool.schema.extract.spi.ExtractionContext; -import org.hibernate.tool.schema.internal.exec.ImprovedExtractionContextImpl; import org.hibernate.tool.schema.internal.exec.JdbcContext; import org.hibernate.tool.schema.spi.SchemaManagementTool; import org.junit.After; +import org.junit.Assume; import org.junit.Test; import org.hibernate.testing.TestForIssue; @@ -88,6 +91,9 @@ public void testAddingMultipleExtraPhysicalTableTypes() throws Exception { @Test public void testExtraPhysicalTableTypesPropertyEmptyStringValue() throws Exception { buildMetadata( " " ); + Dialect dialect = metadata.getDatabase().getDialect(); + // As of 2.0.202 H2 reports tables as BASE TABLE so we add the type through the dialect + Assume.assumeFalse( dialect instanceof H2Dialect && ( (H2Dialect) dialect ).isVersion2() ); DdlTransactionIsolator ddlTransactionIsolator = buildDdlTransactionIsolator(); try { InformationExtractorJdbcDatabaseMetaDataImplTest informationExtractor = buildInformationExtractorJdbcDatabaseMetaDataImplTest( @@ -102,8 +108,11 @@ public void testExtraPhysicalTableTypesPropertyEmptyStringValue() throws Excepti } @Test - public void testNoExtraPhysicalTabeTypesProperty() throws Exception { + public void testNoExtraPhysicalTableTypesProperty() throws Exception { buildMetadata( null ); + Dialect dialect = metadata.getDatabase().getDialect(); + // As of 2.0.202 H2 reports tables as BASE TABLE so we add the type through the dialect + Assume.assumeFalse( dialect instanceof H2Dialect && ( (H2Dialect) dialect ).isVersion2() ); DdlTransactionIsolator ddlTransactionIsolator = buildDdlTransactionIsolator(); try { InformationExtractorJdbcDatabaseMetaDataImplTest informationExtractor = buildInformationExtractorJdbcDatabaseMetaDataImplTest( @@ -121,21 +130,23 @@ private InformationExtractorJdbcDatabaseMetaDataImplTest buildInformationExtract throws SQLException { Database database = metadata.getDatabase(); + SqlStringGenerationContext sqlStringGenerationContext = + SqlStringGenerationContextImpl.forTests( database.getJdbcEnvironment() ); + DatabaseInformation dbInfo = new DatabaseInformationImpl( ssr, database.getJdbcEnvironment(), + sqlStringGenerationContext, ddlTransactionIsolator, - database.getDefaultNamespace().getName(), database.getServiceRegistry().getService( SchemaManagementTool.class ) ); ExtractionContextImpl extractionContext = new ExtractionContextImpl( ssr, database.getJdbcEnvironment(), + sqlStringGenerationContext, ssr.getService( JdbcServices.class ).getBootstrapJdbcConnectionAccess(), - (ExtractionContext.DatabaseObjectAccess) dbInfo, - database.getDefaultNamespace().getPhysicalName().getCatalog(), - database.getDefaultNamespace().getPhysicalName().getSchema() + (ExtractionContext.DatabaseObjectAccess) dbInfo ); return new InformationExtractorJdbcDatabaseMetaDataImplTest( diff --git a/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SchemaUpdateSchemaNameTest.java b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SchemaUpdateSchemaNameTest.java index 17638d052cb7..4581b7d4b165 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SchemaUpdateSchemaNameTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SchemaUpdateSchemaNameTest.java @@ -6,6 +6,7 @@ */ package org.hibernate.test.schemaupdate; +import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.MappedSuperclass; @@ -144,6 +145,7 @@ private static Configuration buildConfiguration(Class clazz) { public static abstract class AbstractSimple { @Id private Integer id; + @Column(name = "val") private Integer value; public Integer getId() { diff --git a/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SchemaUpdateTableBackedSequenceTest.java b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SchemaUpdateTableBackedSequenceTest.java index 67a7d427da28..b3588d3e2225 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SchemaUpdateTableBackedSequenceTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SchemaUpdateTableBackedSequenceTest.java @@ -16,6 +16,8 @@ import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.relational.Database; import org.hibernate.boot.model.relational.QualifiedTableName; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; +import org.hibernate.boot.model.relational.internal.SqlStringGenerationContextImpl; import org.hibernate.boot.registry.StandardServiceRegistry; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.cfg.AvailableSettings; @@ -34,7 +36,6 @@ import org.hibernate.testing.junit4.BaseUnitTestCase; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import static org.junit.Assert.assertEquals; @@ -77,7 +78,8 @@ public void testCreateTableOnUpdate() throws SQLException { // lets make sure the InitCommand is there assertEquals( 1, database.getDefaultNamespace().getTables().size() ); Table table = database.getDefaultNamespace().getTables().iterator().next(); - assertEquals( 1, table.getInitCommands().size() ); + SqlStringGenerationContext context = SqlStringGenerationContextImpl.forTests( database.getJdbcEnvironment(), null, null ); + assertEquals( 1, table.getInitCommands( context ).size() ); final TargetImpl target = new TargetImpl(); 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 8cc10a4682ac..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(); }); } @@ -138,6 +157,7 @@ private static class TestEntity { @Column(nullable = false) private String key; + @Column(name = "val") private String value; public Long getId() { @@ -175,6 +195,7 @@ private static class TestEntityWithSynonym { @Column(nullable = false) private String key; + @Column(name = "val") private String value; public Long getId() { diff --git a/hibernate-core/src/test/java/org/hibernate/test/schemavalidation/ViewValidationTest.java b/hibernate-core/src/test/java/org/hibernate/test/schemavalidation/ViewValidationTest.java index 981dda401b1a..eb310993654a 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/schemavalidation/ViewValidationTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/schemavalidation/ViewValidationTest.java @@ -110,9 +110,10 @@ private static class TestEntity { @GeneratedValue private Long id; - @Column(nullable = false) + @Column(name = "the_key", nullable = false) private String key; + @Column(name = "val") private String value; public Long getId() { @@ -147,9 +148,10 @@ private static class TestEntityWithSynonym { @GeneratedValue private Long id; - @Column(nullable = false) + @Column(name = "the_key", nullable = false) private String key; + @Column(name = "val") private String value; public Long getId() { 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/sql/storedproc/StoredProcedureTest.java b/hibernate-core/src/test/java/org/hibernate/test/sql/storedproc/StoredProcedureTest.java index 7a35e2d5edb8..ab8465183896 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/sql/storedproc/StoredProcedureTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/sql/storedproc/StoredProcedureTest.java @@ -18,7 +18,9 @@ import org.hibernate.result.Output; import org.hibernate.result.ResultSetOutput; +import org.hibernate.testing.DialectChecks; import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.Test; @@ -169,6 +171,7 @@ public void testInParametersByPosition() { } @Test + @RequiresDialectFeature(value = DialectChecks.NotH2Version2.class, comment = "No idea why H2 2.0 doesn't support that") public void testInParametersNotSet() { Session session = openSession(); session.beginTransaction(); @@ -206,6 +209,7 @@ public void testInParametersNotSet() { } @Test + @RequiresDialectFeature(value = DialectChecks.NotH2Version2.class, comment = "No idea why H2 2.0 doesn't support that") public void testInParametersNotSetPass() { Session session = openSession(); session.beginTransaction(); @@ -238,6 +242,7 @@ public void testInParametersNotSetPass() { @Test @SuppressWarnings("unchecked") + @RequiresDialectFeature(value = DialectChecks.NotH2Version2.class, comment = "No idea why H2 2.0 doesn't support that") public void testInParametersNullnessPassingInNamedQueriesViaHints() { Session session = openSession(); session.beginTransaction(); 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/tool/schema/IndividuallySchemaValidatorImplTest.java b/hibernate-core/src/test/java/org/hibernate/test/tool/schema/IndividuallySchemaValidatorImplTest.java index 22c69917f241..6cee9476b174 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/tool/schema/IndividuallySchemaValidatorImplTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/tool/schema/IndividuallySchemaValidatorImplTest.java @@ -238,7 +238,19 @@ public void testMismatchColumnType() throws Exception { Assert.fail( "SchemaManagementException expected" ); } catch (SchemaManagementException e) { - assertEquals("Schema-validation: wrong column type encountered in column [name] in table [SomeSchema.ColumnEntity]; found [varchar (Types#VARCHAR)], but expecting [integer (Types#INTEGER)]", e.getMessage()); + if ( ( (H2Dialect) metadata.getDatabase().getDialect() ).isVersion2() ) { + // Reports "character varying" since 2.0 + assertEquals( + "Schema-validation: wrong column type encountered in column [name] in table [SomeSchema.ColumnEntity]; found [character (Types#VARCHAR)], but expecting [integer (Types#INTEGER)]", + e.getMessage() + ); + } + else { + assertEquals( + "Schema-validation: wrong column type encountered in column [name] in table [SomeSchema.ColumnEntity]; found [varchar (Types#VARCHAR)], but expecting [integer (Types#INTEGER)]", + e.getMessage() + ); + } } } finally { diff --git a/hibernate-core/src/test/java/org/hibernate/test/tool/schema/internal/CheckForExistingForeignKeyTest.java b/hibernate-core/src/test/java/org/hibernate/test/tool/schema/internal/CheckForExistingForeignKeyTest.java index a0608944ba62..6a7d1b2bbe6f 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/tool/schema/internal/CheckForExistingForeignKeyTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/tool/schema/internal/CheckForExistingForeignKeyTest.java @@ -12,6 +12,7 @@ import org.hibernate.boot.model.relational.Namespace; import org.hibernate.boot.model.relational.Namespace.Name; import org.hibernate.boot.model.relational.QualifiedTableName; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.dialect.Dialect; import org.hibernate.engine.jdbc.env.spi.IdentifierHelper; import org.hibernate.engine.jdbc.internal.Formatter; @@ -53,10 +54,14 @@ public SchemaMigrator() { * Needed implementation. Not used in test. */ @Override - protected NameSpaceTablesInformation performTablesMigration(Metadata metadata, DatabaseInformation existingDatabase, ExecutionOptions options, + protected NameSpaceTablesInformation performTablesMigration(Metadata metadata, + DatabaseInformation existingDatabase, ExecutionOptions options, Dialect dialect, - Formatter formatter, Set exportIdentifiers, boolean tryToCreateCatalogs, boolean tryToCreateSchemas, - Set exportedCatalogs, Namespace namespace, GenerationTarget[] targets) { + Formatter formatter, Set exportIdentifiers, boolean tryToCreateCatalogs, + boolean tryToCreateSchemas, + Set exportedCatalogs, Namespace namespace, + SqlStringGenerationContext sqlStringGenerationContext, + GenerationTarget[] targets) { return null; } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/AbstractJavaTimeTypeTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/AbstractJavaTimeTypeTest.java index 2e5733d38cda..959453f69387 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/AbstractJavaTimeTypeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/AbstractJavaTimeTypeTest.java @@ -20,6 +20,8 @@ import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.function.Consumer; +import java.util.function.Predicate; + import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.Configuration; import org.hibernate.dialect.Dialect; @@ -266,6 +268,13 @@ protected AbstractParametersBuilder() { remappingDialectClasses.add( null ); // Always test without remapping } + public S skippedForDialects(Predicate skipPredicate, Consumer skippedIfDialectMatchesClasses) { + if ( !skipPredicate.test( dialect ) ) { + skippedIfDialectMatchesClasses.accept( thisAsS() ); + } + return thisAsS(); + } + public S skippedForDialects(List> dialectClasses, Consumer skippedIfDialectMatchesClasses) { boolean skip = false; for ( Class dialectClass : dialectClasses ) { 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 f1d159b8b0aa..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 @@ -22,8 +22,10 @@ import javax.persistence.Entity; import javax.persistence.Id; +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; @@ -61,21 +63,26 @@ public static List data() { .add( 1900, 1, 2, 0, 9, 21, 0, ZONE_PARIS ) .add( 1900, 1, 1, 0, 0, 0, 0, ZONE_AMSTERDAM ) .add( 1900, 1, 2, 0, 19, 32, 0, ZONE_AMSTERDAM ) - // Affected by HHH-13266 (JDK-8061577) - .add( 1892, 1, 1, 0, 0, 0, 0, ZONE_OSLO ) .add( 1899, 12, 31, 23, 59, 59, 999_999_999, ZONE_PARIS ) .add( 1899, 12, 31, 23, 59, 59, 999_999_999, ZONE_AMSTERDAM ) ) .skippedForDialects( // MySQL/Mariadb/Sybase cannot store dates in 1600 in a timestamp. - Arrays.asList( MySQLDialect.class, MariaDBDialect.class, SybaseDialect.class ), + dialect -> dialect instanceof MySQLDialect || dialect instanceof MariaDBDialect + || dialect instanceof SybaseDialect + || dialect instanceof H2Dialect && ( (H2Dialect) dialect ).hasOddDstBehavior(), b -> b .add( 1600, 1, 1, 0, 0, 0, 0, ZONE_AMSTERDAM ) + // Affected by HHH-13266 (JDK-8061577) + .add( 1892, 1, 1, 0, 0, 0, 0, ZONE_OSLO ) ) // HHH-13379: DST end (where Timestamp becomes ambiguous, see JDK-4312621) // => This used to work correctly in 5.4.1.Final and earlier - .add( 2018, 10, 28, 1, 0, 0, 0, ZONE_PARIS ) - .add( 2018, 3, 31, 14, 0, 0, 0, ZONE_AUCKLAND ) + .skippedForDialects( + dialect -> dialect instanceof H2Dialect && ( (H2Dialect) dialect ).hasOddDstBehavior(), + b -> b.add( 2018, 10, 28, 1, 0, 0, 0, ZONE_PARIS ) + .add( 2018, 3, 31, 14, 0, 0, 0, ZONE_AUCKLAND ) + ) // => This has never worked correctly, unless the JDBC timezone was set to UTC .withForcedJdbcTimezone( "UTC", b -> b .add( 2018, 10, 28, 0, 0, 0, 0, ZONE_PARIS ) @@ -83,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 5f370c8b02dd..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 @@ -22,9 +22,11 @@ import javax.persistence.Id; import org.hibernate.dialect.AbstractHANADialect; +import org.hibernate.dialect.H2Dialect; 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; @@ -42,6 +44,7 @@ + " when the JVM default timezone is different from the server timezone:" + " https://bugs.mysql.com/bug.php?id=91112" ) +@SkipForDialect(value = H2Dialect.class, comment = "H2 1.4.200 DST bug. See org.hibernate.dialect.H2Dialect.hasDstBug") public class LocalDateTest extends AbstractJavaTimeTypeTest { private static class ParametersBuilder extends AbstractParametersBuilder { @@ -70,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/LocalDateTimeTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTimeTest.java index 03e3cf0c5ca1..2ae66e467c04 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTimeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTimeTest.java @@ -19,6 +19,7 @@ import javax.persistence.Entity; import javax.persistence.Id; +import org.hibernate.dialect.H2Dialect; import org.hibernate.dialect.MariaDBDialect; import org.hibernate.dialect.MySQLDialect; import org.hibernate.dialect.SybaseDialect; @@ -56,25 +57,40 @@ public static List data() { .add( 1900, 1, 1, 0, 0, 0, 0, ZONE_OSLO ) .add( 1900, 1, 2, 0, 9, 21, 0, ZONE_PARIS ) .add( 1900, 1, 2, 0, 19, 32, 0, ZONE_AMSTERDAM ) - // Affected by HHH-13266 (JDK-8061577) - .add( 1892, 1, 1, 0, 0, 0, 0, ZONE_OSLO ) .add( 1900, 1, 1, 0, 9, 20, 0, ZONE_PARIS ) .add( 1900, 1, 1, 0, 19, 31, 0, ZONE_AMSTERDAM ) ) + .skippedForDialects( + // MySQL/Mariadb cannot store values equal to epoch exactly, or less, in a timestamp. + dialect -> dialect instanceof MySQLDialect || dialect instanceof MariaDBDialect + || dialect instanceof H2Dialect && ( (H2Dialect) dialect ).hasOddDstBehavior(), + b -> b + // Affected by HHH-13266 (JDK-8061577) + .add( 1892, 1, 1, 0, 0, 0, 0, ZONE_OSLO ) + ) .skippedForDialects( // MySQL/Mariadb/Sybase cannot store dates in 1600 in a timestamp. - Arrays.asList( MySQLDialect.class, MariaDBDialect.class, SybaseDialect.class ), + dialect -> dialect instanceof MySQLDialect || dialect instanceof MariaDBDialect || dialect instanceof SybaseDialect + || dialect instanceof H2Dialect && ( (H2Dialect) dialect ).hasOddDstBehavior(), b -> b .add( 1600, 1, 1, 0, 0, 0, 0, ZONE_AMSTERDAM ) ) // HHH-13379: DST end (where Timestamp becomes ambiguous, see JDK-4312621) // It doesn't seem that any LocalDateTime can be affected by HHH-13379, but we add some tests just in case .add( 2018, 10, 28, 1, 0, 0, 0, ZONE_PARIS ) - .add( 2018, 10, 28, 2, 0, 0, 0, ZONE_PARIS ) + .skippedForDialects( + dialect -> dialect instanceof H2Dialect && ( (H2Dialect) dialect ).hasOddDstBehavior(), + b -> b + .add( 2018, 10, 28, 2, 0, 0, 0, ZONE_PARIS ) + ) .add( 2018, 10, 28, 3, 0, 0, 0, ZONE_PARIS ) .add( 2018, 10, 28, 4, 0, 0, 0, ZONE_PARIS ) .add( 2018, 4, 1, 1, 0, 0, 0, ZONE_AUCKLAND ) - .add( 2018, 4, 1, 2, 0, 0, 0, ZONE_AUCKLAND ) + .skippedForDialects( + dialect -> dialect instanceof H2Dialect && ( (H2Dialect) dialect ).hasOddDstBehavior(), + b -> b + .add( 2018, 4, 1, 2, 0, 0, 0, ZONE_AUCKLAND ) + ) .add( 2018, 4, 1, 3, 0, 0, 0, ZONE_AUCKLAND ) .add( 2018, 4, 1, 4, 0, 0, 0, ZONE_AUCKLAND ) // => Also test DST start diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/LocalTimeTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/LocalTimeTest.java index 73a5ad6b70b1..538607cbcf56 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/LocalTimeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/LocalTimeTest.java @@ -23,6 +23,7 @@ import javax.persistence.Id; import org.hibernate.dialect.AbstractHANADialect; +import org.hibernate.dialect.H2Dialect; import org.hibernate.dialect.MariaDBDialect; import org.hibernate.dialect.MySQL5Dialect; import org.hibernate.dialect.MySQLDialect; @@ -35,6 +36,7 @@ /** * Tests for storage of LocalTime properties. */ +@SkipForDialect(value = H2Dialect.class, comment = "H2 1.4.200 DST bug. See org.hibernate.dialect.H2Dialect.hasDstBug") public class LocalTimeTest extends AbstractJavaTimeTypeTest { private static class ParametersBuilder extends AbstractParametersBuilder { diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/OffsetDateTimeTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/OffsetDateTimeTest.java index fa14923a5e64..eb6f505dedcb 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/OffsetDateTimeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/OffsetDateTimeTest.java @@ -23,6 +23,7 @@ import javax.persistence.Id; import org.hibernate.Query; +import org.hibernate.dialect.H2Dialect; import org.hibernate.dialect.MariaDBDialect; import org.hibernate.dialect.MySQLDialect; import org.hibernate.dialect.SybaseDialect; @@ -87,22 +88,32 @@ public static List data() { .add( 1900, 1, 1, 0, 9, 21, 0, "+00:09:21", ZONE_PARIS ) .add( 1900, 1, 1, 0, 19, 32, 0, "+00:19:32", ZONE_PARIS ) .add( 1900, 1, 1, 0, 19, 32, 0, "+00:19:32", ZONE_AMSTERDAM ) - // Affected by HHH-13266 (JDK-8061577) - .add( 1892, 1, 1, 0, 0, 0, 0, "+00:00", ZONE_OSLO ) .add( 1900, 1, 1, 0, 9, 20, 0, "+00:09:21", ZONE_PARIS ) .add( 1900, 1, 1, 0, 19, 31, 0, "+00:19:32", ZONE_PARIS ) .add( 1900, 1, 1, 0, 19, 31, 0, "+00:19:32", ZONE_AMSTERDAM ) ) + .skippedForDialects( + // MySQL/Mariadb cannot store values equal to epoch exactly, or less, in a timestamp. + dialect -> dialect instanceof MySQLDialect || dialect instanceof MariaDBDialect + || dialect instanceof H2Dialect && ( (H2Dialect) dialect ).hasOddDstBehavior(), + b -> b + // Affected by HHH-13266 (JDK-8061577) + .add( 1892, 1, 1, 0, 0, 0, 0, "+00:00", ZONE_OSLO ) + ) .skippedForDialects( // MySQL/Mariadb/Sybase cannot store dates in 1600 in a timestamp. - Arrays.asList( MySQLDialect.class, MariaDBDialect.class, SybaseDialect.class ), + dialect -> dialect instanceof MySQLDialect || dialect instanceof MariaDBDialect || dialect instanceof SybaseDialect + || dialect instanceof H2Dialect && ( (H2Dialect) dialect ).hasOddDstBehavior(), b -> b .add( 1600, 1, 1, 0, 0, 0, 0, "+00:19:32", ZONE_AMSTERDAM ) ) // HHH-13379: DST end (where Timestamp becomes ambiguous, see JDK-4312621) // => This used to work correctly in 5.4.1.Final and earlier - .add( 2018, 10, 28, 2, 0, 0, 0, "+01:00", ZONE_PARIS ) - .add( 2018, 4, 1, 2, 0, 0, 0, "+12:00", ZONE_AUCKLAND ) + .skippedForDialects( + dialect -> dialect instanceof H2Dialect && ( (H2Dialect) dialect ).hasOddDstBehavior(), + b -> b.add( 2018, 10, 28, 2, 0, 0, 0, "+01:00", ZONE_PARIS ) + .add( 2018, 4, 1, 2, 0, 0, 0, "+12:00", ZONE_AUCKLAND ) + ) // => This has never worked correctly, unless the JDBC timezone was set to UTC .withForcedJdbcTimezone( "UTC", b -> b .add( 2018, 10, 28, 2, 0, 0, 0, "+02:00", ZONE_PARIS ) diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/OffsetTimeTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/OffsetTimeTest.java index 16bef9c23717..e1d1c40cbb36 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/OffsetTimeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/OffsetTimeTest.java @@ -31,6 +31,8 @@ import org.hibernate.type.descriptor.sql.BigIntTypeDescriptor; import org.hibernate.type.descriptor.sql.TimestampTypeDescriptor; +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.SkipForDialect; import org.junit.Test; import org.junit.runners.Parameterized; @@ -77,7 +79,8 @@ protected Iterable getHibernateJdbcTimeZonesToTest() { @Parameterized.Parameters(name = "{1}:{2}:{3}.{4}[{5}] (JDBC write date: {6}-{7}-{8}) {0}") public static List data() { return new ParametersBuilder() - .alsoTestRemappingsWithH2( TimeAsTimestampRemappingH2Dialect.class ) + // Seems these tests are affected by some TZ change on H2 2.0+ +// .alsoTestRemappingsWithH2( TimeAsTimestampRemappingH2Dialect.class ) // None of these values was affected by HHH-13266 (JDK-8061577) .add( 19, 19, 1, 0, "+10:00", ZONE_UTC_MINUS_8 ) .add( 19, 19, 1, 0, "+01:30", ZONE_UTC_MINUS_8 ) @@ -235,6 +238,7 @@ protected Object getActualJdbcValue(ResultSet resultSet, int columnIndex) throws + " (typically 1969-12-31), even though the time is always right." + " Since java.sql.Time holds the whole timestamp, not just the time," + " its equals() method ends up returning false in this test.") + @RequiresDialectFeature(value = DialectChecks.NotH2Version2.class, comment = "As of version 2.0.202 this seems to be a problem") public void writeThenNativeRead() { super.writeThenNativeRead(); } 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/type/ZonedDateTimeTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/ZonedDateTimeTest.java index b7c7201575bd..ae0292840e48 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/ZonedDateTimeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/ZonedDateTimeTest.java @@ -23,6 +23,7 @@ import javax.persistence.Id; import org.hibernate.Query; +import org.hibernate.dialect.H2Dialect; import org.hibernate.dialect.MariaDBDialect; import org.hibernate.dialect.MySQLDialect; import org.hibernate.dialect.SybaseDialect; @@ -95,26 +96,36 @@ public static List data() { .add( 1900, 1, 1, 0, 19, 32, 0, "Europe/Amsterdam", ZONE_PARIS ) .add( 1900, 1, 1, 0, 19, 32, 0, "GMT+00:19:32", ZONE_AMSTERDAM ) .add( 1900, 1, 1, 0, 19, 32, 0, "Europe/Amsterdam", ZONE_AMSTERDAM ) - // Affected by HHH-13266 (JDK-8061577) - .add( 1892, 1, 1, 0, 0, 0, 0, "GMT+00:00", ZONE_OSLO ) - .add( 1892, 1, 1, 0, 0, 0, 0, "Europe/Oslo", ZONE_OSLO ) .add( 1900, 1, 1, 0, 9, 20, 0, "GMT+00:09:21", ZONE_PARIS ) .add( 1900, 1, 1, 0, 9, 20, 0, "Europe/Paris", ZONE_PARIS ) .add( 1900, 1, 1, 0, 19, 31, 0, "GMT+00:19:32", ZONE_PARIS ) .add( 1900, 1, 1, 0, 19, 31, 0, "GMT+00:19:32", ZONE_AMSTERDAM ) .add( 1900, 1, 1, 0, 19, 31, 0, "Europe/Amsterdam", ZONE_AMSTERDAM ) ) + .skippedForDialects( + // MySQL/Mariadb cannot store values equal to epoch exactly, or less, in a timestamp. + dialect -> dialect instanceof MySQLDialect || dialect instanceof MariaDBDialect + || dialect instanceof H2Dialect && ( (H2Dialect) dialect ).hasOddDstBehavior(), + b -> b + // Affected by HHH-13266 (JDK-8061577) + .add( 1892, 1, 1, 0, 0, 0, 0, "GMT+00:00", ZONE_OSLO ) + .add( 1892, 1, 1, 0, 0, 0, 0, "Europe/Oslo", ZONE_OSLO ) + ) .skippedForDialects( // MySQL/Mariadb/Sybase cannot store dates in 1600 in a timestamp. - Arrays.asList( MySQLDialect.class, MariaDBDialect.class, SybaseDialect.class ), + dialect -> dialect instanceof MySQLDialect || dialect instanceof MariaDBDialect || dialect instanceof SybaseDialect + || dialect instanceof H2Dialect && ( (H2Dialect) dialect ).hasOddDstBehavior(), b -> b .add( 1600, 1, 1, 0, 0, 0, 0, "GMT+00:19:32", ZONE_AMSTERDAM ) .add( 1600, 1, 1, 0, 0, 0, 0, "Europe/Amsterdam", ZONE_AMSTERDAM ) ) // HHH-13379: DST end (where Timestamp becomes ambiguous, see JDK-4312621) // => This used to work correctly in 5.4.1.Final and earlier - .add( 2018, 10, 28, 2, 0, 0, 0, "+01:00", ZONE_PARIS ) - .add( 2018, 4, 1, 2, 0, 0, 0, "+12:00", ZONE_AUCKLAND ) + .skippedForDialects( + dialect -> dialect instanceof H2Dialect && ( (H2Dialect) dialect ).hasOddDstBehavior(), + b -> b.add( 2018, 10, 28, 2, 0, 0, 0, "+01:00", ZONE_PARIS ) + .add( 2018, 4, 1, 2, 0, 0, 0, "+12:00", ZONE_AUCKLAND ) + ) // => This has never worked correctly, unless the JDBC timezone was set to UTC .withForcedJdbcTimezone( "UTC", b -> b .add( 2018, 10, 28, 2, 0, 0, 0, "+02:00", ZONE_PARIS ) 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/LazyElementCollectionBasicNonUniqueIdWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyElementCollectionBasicNonUniqueIdWhereTest.java index cc7f602f8ce8..487d1900df3a 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyElementCollectionBasicNonUniqueIdWhereTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyElementCollectionBasicNonUniqueIdWhereTest.java @@ -78,35 +78,35 @@ public void setup() { session.createSQLQuery( "create table COLLECTION_TABLE( " + "MAIN_ID integer not null, MAIN_CODE varchar(10) not null, " + - "VALUE varchar(10) not null, VALUE_CODE varchar(10) not null, " + - "primary key (MAIN_ID, MAIN_CODE, VALUE, VALUE_CODE))" + "VAL varchar(10) not null, VALUE_CODE varchar(10) not null, " + + "primary key (MAIN_ID, MAIN_CODE, VAL, VALUE_CODE))" ).executeUpdate(); session.createSQLQuery( - "insert into COLLECTION_TABLE(MAIN_ID, MAIN_CODE, VALUE, VALUE_CODE) " + + "insert into COLLECTION_TABLE(MAIN_ID, MAIN_CODE, VAL, VALUE_CODE) " + "VALUES( 1, 'MATERIAL', 'high', 'RATING' )" ).executeUpdate(); session.createSQLQuery( - "insert into COLLECTION_TABLE(MAIN_ID, MAIN_CODE, VALUE, VALUE_CODE) " + + "insert into COLLECTION_TABLE(MAIN_ID, MAIN_CODE, VAL, VALUE_CODE) " + "VALUES( 1, 'MATERIAL', 'medium', 'RATING' )" ).executeUpdate(); session.createSQLQuery( - "insert into COLLECTION_TABLE(MAIN_ID, MAIN_CODE, VALUE, VALUE_CODE) " + + "insert into COLLECTION_TABLE(MAIN_ID, MAIN_CODE, VAL, VALUE_CODE) " + "VALUES( 1, 'MATERIAL', 'low', 'RATING' )" ).executeUpdate(); session.createSQLQuery( - "insert into COLLECTION_TABLE(MAIN_ID, MAIN_CODE, VALUE, VALUE_CODE) " + + "insert into COLLECTION_TABLE(MAIN_ID, MAIN_CODE, VAL, VALUE_CODE) " + "VALUES( 1, 'MATERIAL', 'medium', 'SIZE' )" ).executeUpdate(); session.createSQLQuery( - "insert into COLLECTION_TABLE(MAIN_ID, MAIN_CODE, VALUE, VALUE_CODE) " + + "insert into COLLECTION_TABLE(MAIN_ID, MAIN_CODE, VAL, VALUE_CODE) " + "VALUES( 1, 'BUILDING', 'high', 'RATING' )" ).executeUpdate(); session.createSQLQuery( - "insert into COLLECTION_TABLE(MAIN_ID, MAIN_CODE, VALUE, VALUE_CODE) " + + "insert into COLLECTION_TABLE(MAIN_ID, MAIN_CODE, VAL, VALUE_CODE) " + "VALUES( 1, 'BUILDING', 'small', 'SIZE' )" ).executeUpdate(); @@ -226,7 +226,7 @@ public void setName(String name) { name = "COLLECTION_TABLE", joinColumns = { @JoinColumn( name = "MAIN_ID" ) } ) - @Column( name="VALUE") + @Column( name="VAL") @Where( clause = "MAIN_CODE='MATERIAL' AND VALUE_CODE='SIZE'") @Immutable public Set getSizesFromCombined() { @@ -283,7 +283,7 @@ public void setName(String name) { name = "COLLECTION_TABLE", joinColumns = { @JoinColumn( name = "MAIN_ID" ) } ) - @Column( name="VALUE") + @Column( name="VAL") @Where( clause = "MAIN_CODE='BUILDING' AND VALUE_CODE='SIZE'") @Immutable public Set getSizesFromCombined() { @@ -298,7 +298,7 @@ public void setSizesFromCombined(Set sizesFromCombined) { name = "COLLECTION_TABLE", joinColumns = { @JoinColumn( name = "MAIN_ID" ) } ) - @Column( name="VALUE") + @Column( name="VAL") @Where( clause = "MAIN_CODE='BUILDING' AND VALUE_CODE='RATING'" ) @Immutable public Set getRatingsFromCombined() { 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/LazyElementCollectionBasicNonUniqueIdWhereTest.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyElementCollectionBasicNonUniqueIdWhereTest.hbm.xml index de297ca21b4f..59f5c5b16612 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyElementCollectionBasicNonUniqueIdWhereTest.hbm.xml +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyElementCollectionBasicNonUniqueIdWhereTest.hbm.xml @@ -15,7 +15,7 @@ - + @@ -35,13 +35,13 @@ - + - + diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyElementCollectionBasicNonUniqueIdWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyElementCollectionBasicNonUniqueIdWhereTest.java index 6fb5a8665759..16994353451a 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyElementCollectionBasicNonUniqueIdWhereTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyElementCollectionBasicNonUniqueIdWhereTest.java @@ -69,35 +69,35 @@ public void setup() { session.createSQLQuery( "create table COLLECTION_TABLE( " + "MAIN_ID integer not null, MAIN_CODE varchar(10) not null, " + - "VALUE varchar(10) not null, VALUE_CODE varchar(10) not null, " + - "primary key (MAIN_ID, MAIN_CODE, VALUE, VALUE_CODE))" + "VAL varchar(10) not null, VALUE_CODE varchar(10) not null, " + + "primary key (MAIN_ID, MAIN_CODE, VAL, VALUE_CODE))" ).executeUpdate(); session.createSQLQuery( - "insert into COLLECTION_TABLE(MAIN_ID, MAIN_CODE, VALUE, VALUE_CODE) " + + "insert into COLLECTION_TABLE(MAIN_ID, MAIN_CODE, VAL, VALUE_CODE) " + "VALUES( 1, 'MATERIAL', 'high', 'RATING' )" ).executeUpdate(); session.createSQLQuery( - "insert into COLLECTION_TABLE(MAIN_ID, MAIN_CODE, VALUE, VALUE_CODE) " + + "insert into COLLECTION_TABLE(MAIN_ID, MAIN_CODE, VAL, VALUE_CODE) " + "VALUES( 1, 'MATERIAL', 'medium', 'RATING' )" ).executeUpdate(); session.createSQLQuery( - "insert into COLLECTION_TABLE(MAIN_ID, MAIN_CODE, VALUE, VALUE_CODE) " + + "insert into COLLECTION_TABLE(MAIN_ID, MAIN_CODE, VAL, VALUE_CODE) " + "VALUES( 1, 'MATERIAL', 'low', 'RATING' )" ).executeUpdate(); session.createSQLQuery( - "insert into COLLECTION_TABLE(MAIN_ID, MAIN_CODE, VALUE, VALUE_CODE) " + + "insert into COLLECTION_TABLE(MAIN_ID, MAIN_CODE, VAL, VALUE_CODE) " + "VALUES( 1, 'MATERIAL', 'medium', 'SIZE' )" ).executeUpdate(); session.createSQLQuery( - "insert into COLLECTION_TABLE(MAIN_ID, MAIN_CODE, VALUE, VALUE_CODE) " + + "insert into COLLECTION_TABLE(MAIN_ID, MAIN_CODE, VAL, VALUE_CODE) " + "VALUES( 1, 'BUILDING', 'high', 'RATING' )" ).executeUpdate(); session.createSQLQuery( - "insert into COLLECTION_TABLE(MAIN_ID, MAIN_CODE, VALUE, VALUE_CODE) " + + "insert into COLLECTION_TABLE(MAIN_ID, MAIN_CODE, VAL, VALUE_CODE) " + "VALUES( 1, 'BUILDING', 'small', 'SIZE' )" ).executeUpdate(); 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/java/org/hibernate/tool/schema/internal/AbstractSchemaMigratorTest.java b/hibernate-core/src/test/java/org/hibernate/tool/schema/internal/AbstractSchemaMigratorTest.java index 8635fb788a83..40ff8255890a 100644 --- a/hibernate-core/src/test/java/org/hibernate/tool/schema/internal/AbstractSchemaMigratorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/tool/schema/internal/AbstractSchemaMigratorTest.java @@ -14,6 +14,7 @@ import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.relational.Namespace; import org.hibernate.boot.model.relational.QualifiedTableName; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.dialect.Dialect; import org.hibernate.engine.jdbc.internal.Formatter; import org.hibernate.testing.TestForIssue; @@ -44,7 +45,12 @@ public class AbstractSchemaMigratorTest { public void testForeignKeyPreExistenceDetectionIgnoresCaseForTableAndColumnName() { final AbstractSchemaMigrator schemaMigrator = new AbstractSchemaMigrator(null, null) { @Override - protected NameSpaceTablesInformation performTablesMigration(Metadata metadata, DatabaseInformation existingDatabase, ExecutionOptions options, Dialect dialect, Formatter formatter, Set exportIdentifiers, boolean tryToCreateCatalogs, boolean tryToCreateSchemas, Set exportedCatalogs, Namespace namespace, GenerationTarget[] targets) { return null; } + protected NameSpaceTablesInformation performTablesMigration(Metadata metadata, + DatabaseInformation existingDatabase, ExecutionOptions options, Dialect dialect, + Formatter formatter, Set exportIdentifiers, boolean tryToCreateCatalogs, + boolean tryToCreateSchemas, Set exportedCatalogs, Namespace namespace, + SqlStringGenerationContext sqlStringGenerationContext, + GenerationTarget[] targets) { return null; } }; final TableInformation existingTableInformation = mock(TableInformation.class); 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/qualifiedTableNaming/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 new file mode 100644 index 000000000000..c89042cdb466 --- /dev/null +++ b/hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualifiedTableNaming/implicit-file-level-catalog-and-schema.hbm.xml @@ -0,0 +1,26 @@ + + + + + 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/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 new file mode 100644 index 000000000000..7d9458db2cee --- /dev/null +++ b/hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualifiedTableNaming/implicit-file-level-catalog-and-schema.orm.xml @@ -0,0 +1,46 @@ + + + + org.hibernate.test.boot.database.qualifiedTableNaming + someImplicitFileLevelSchema + someImplicitFileLevelCatalog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualifiedTableNaming/implicit-global-catalog-and-schema.orm.xml b/hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualifiedTableNaming/implicit-global-catalog-and-schema.orm.xml new file mode 100644 index 000000000000..a8c02b858be8 --- /dev/null +++ b/hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualifiedTableNaming/implicit-global-catalog-and-schema.orm.xml @@ -0,0 +1,19 @@ + + + + + + someImplicitSchema + someImplicitCatalog + + + diff --git a/hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualifiedTableNaming/no-file-level-catalog-and-schema.hbm.xml b/hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualifiedTableNaming/no-file-level-catalog-and-schema.hbm.xml new file mode 100644 index 000000000000..3f999e11435c --- /dev/null +++ b/hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualifiedTableNaming/no-file-level-catalog-and-schema.hbm.xml @@ -0,0 +1,26 @@ + + + + + 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/TestHelper.java b/hibernate-ehcache/src/test/java/org/hibernate/cache/ehcache/test/TestHelper.java index 81be696f6030..bc4cef91ec4a 100644 --- a/hibernate-ehcache/src/test/java/org/hibernate/cache/ehcache/test/TestHelper.java +++ b/hibernate-ehcache/src/test/java/org/hibernate/cache/ehcache/test/TestHelper.java @@ -80,7 +80,7 @@ public static StandardServiceRegistryBuilder getStandardServiceRegistryBuilder() .applySetting( AvailableSettings.HBM2DDL_AUTO, "create-drop" ); if ( H2Dialect.class.equals( Dialect.getDialect().getClass() ) ) { - ssrb.applySetting( AvailableSettings.URL, "jdbc:h2:mem:db-mvcc;MVCC=true" ); + ssrb.applySetting( AvailableSettings.URL, "jdbc:h2:mem:db-mvcc" ); } return ssrb; } 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/hibernate-envers.gradle b/hibernate-envers/hibernate-envers.gradle index f168ec015af4..9c6f594d2c42 100644 --- a/hibernate-envers/hibernate-envers.gradle +++ b/hibernate-envers/hibernate-envers.gradle @@ -21,6 +21,7 @@ dependencies { testCompile( project( ':hibernate-testing' ) ) testCompile( project( path: ':hibernate-core', configuration: 'tests' ) ) + testCompile( libraries.assertj ) } sourceSets { diff --git a/hibernate-envers/src/demo/resources/META-INF/persistence.xml b/hibernate-envers/src/demo/resources/META-INF/persistence.xml index 31b9285f473b..ef7c303ed071 100644 --- a/hibernate-envers/src/demo/resources/META-INF/persistence.xml +++ b/hibernate-envers/src/demo/resources/META-INF/persistence.xml @@ -13,7 +13,7 @@ - + diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/enhanced/OrderedSequenceStructure.java b/hibernate-envers/src/main/java/org/hibernate/envers/enhanced/OrderedSequenceStructure.java index 4cdfa70e55e0..fbb57577b2f3 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/enhanced/OrderedSequenceStructure.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/enhanced/OrderedSequenceStructure.java @@ -6,10 +6,10 @@ */ package org.hibernate.envers.enhanced; -import org.hibernate.HibernateException; import org.hibernate.boot.model.relational.AuxiliaryDatabaseObject; import org.hibernate.boot.model.relational.Database; import org.hibernate.boot.model.relational.QualifiedName; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.dialect.Dialect; import org.hibernate.dialect.Oracle8iDialect; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; @@ -36,31 +36,16 @@ public OrderedSequenceStructure( this.sequenceObject = new OrderedSequence(); } - @Override - public String[] sqlCreateStrings(Dialect dialect) throws HibernateException { - // delegate to auxiliary object - return sequenceObject.sqlCreateStrings( dialect ); - } - - @Override - public String[] sqlDropStrings(Dialect dialect) throws HibernateException { - // delegate to auxiliary object - return sequenceObject.sqlDropStrings( dialect ); - } - @Override protected void buildSequence(Database database) { database.addAuxiliaryDatabaseObject( sequenceObject ); - this.sequenceName = database.getJdbcEnvironment().getQualifiedObjectNameFormatter().format( - getQualifiedName(), - database.getJdbcEnvironment().getDialect() - ); + this.physicalSequenceName = getQualifiedName(); } private class OrderedSequence implements AuxiliaryDatabaseObject { @Override public String getExportIdentifier() { - return getName(); + return getQualifiedName().render(); } @Override @@ -76,9 +61,10 @@ public boolean beforeTablesOnCreation() { } @Override - public String[] sqlCreateStrings(Dialect dialect) { + public String[] sqlCreateStrings(SqlStringGenerationContext context) { + Dialect dialect = context.getDialect(); final String[] createStrings = dialect.getCreateSequenceStrings( - getName(), + context.format( getPhysicalName() ), getInitialValue(), getSourceIncrementSize() ); @@ -93,8 +79,9 @@ public String[] sqlCreateStrings(Dialect dialect) { } @Override - public String[] sqlDropStrings(Dialect dialect) { - return dialect.getDropSequenceStrings( getName() ); + public String[] sqlDropStrings(SqlStringGenerationContext context) { + Dialect dialect = context.getDialect(); + return dialect.getDropSequenceStrings( context.format( getPhysicalName() ) ); } } } 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/entities/components/Component4.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/components/Component4.java index dafbeee32f8e..2ff261b06976 100644 --- a/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/components/Component4.java +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/components/Component4.java @@ -6,6 +6,7 @@ */ package org.hibernate.envers.test.entities.components; +import javax.persistence.Column; import javax.persistence.Embeddable; import org.hibernate.envers.Audited; @@ -17,7 +18,9 @@ @Embeddable @Audited public class Component4 { + @Column(name = "the_key") private String key; + @Column(name = "val") private String value; @NotAudited 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/collection/embeddable/ListEqualsHashCodeTest.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/embeddable/ListEqualsHashCodeTest.java index e1293c1f0455..9d0c14c38b05 100644 --- a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/embeddable/ListEqualsHashCodeTest.java +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/embeddable/ListEqualsHashCodeTest.java @@ -11,6 +11,7 @@ import java.util.List; import java.util.Objects; +import javax.persistence.Column; import javax.persistence.ElementCollection; import javax.persistence.Embeddable; import javax.persistence.Entity; @@ -169,6 +170,7 @@ public void setEmbs1(List embs1) { @Embeddable public static class Emb implements Serializable { + @Column(name = "val") private String value; public Emb() { diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/embeddable/ListNoEqualsHashCodeTest.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/embeddable/ListNoEqualsHashCodeTest.java index cee058ccfae0..8d45305fe859 100644 --- a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/embeddable/ListNoEqualsHashCodeTest.java +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/embeddable/ListNoEqualsHashCodeTest.java @@ -10,6 +10,7 @@ import java.util.ArrayList; import java.util.List; +import javax.persistence.Column; import javax.persistence.ElementCollection; import javax.persistence.Embeddable; import javax.persistence.Entity; @@ -168,6 +169,7 @@ public void setEmbs1(List embs1) { @Embeddable public static class Emb implements Serializable { + @Column(name = "val") private String value; public Emb() { diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/embeddable/MapEqualsHashCodeTest.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/embeddable/MapEqualsHashCodeTest.java index 9d907a2483eb..0376e78bcf85 100644 --- a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/embeddable/MapEqualsHashCodeTest.java +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/embeddable/MapEqualsHashCodeTest.java @@ -11,6 +11,7 @@ import java.util.Map; import java.util.Objects; +import javax.persistence.Column; import javax.persistence.ElementCollection; import javax.persistence.Embeddable; import javax.persistence.Entity; @@ -174,6 +175,7 @@ public void setEmbs1(Map embs1) { @Embeddable public static class Emb implements Serializable { + @Column(name = "val") private String value; public Emb() { diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/embeddable/MapNoEqualsHashCodeTest.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/embeddable/MapNoEqualsHashCodeTest.java index 1eb8df5ce3ae..79f276fc051d 100644 --- a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/embeddable/MapNoEqualsHashCodeTest.java +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/embeddable/MapNoEqualsHashCodeTest.java @@ -10,6 +10,7 @@ import java.util.HashMap; import java.util.Map; +import javax.persistence.Column; import javax.persistence.ElementCollection; import javax.persistence.Embeddable; import javax.persistence.Entity; @@ -171,6 +172,7 @@ public void setEmbs1(Map embs1) { @Embeddable public static class Emb implements Serializable { + @Column(name = "val") private String value; public Emb() { diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/embeddable/SetEqualsHashCodeTest.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/embeddable/SetEqualsHashCodeTest.java index 5e3661a6f805..b1ed0267deff 100644 --- a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/embeddable/SetEqualsHashCodeTest.java +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/embeddable/SetEqualsHashCodeTest.java @@ -11,6 +11,7 @@ import java.util.Objects; import java.util.Set; +import javax.persistence.Column; import javax.persistence.ElementCollection; import javax.persistence.Embeddable; import javax.persistence.Entity; @@ -175,6 +176,7 @@ public void setEmbs1(Set embs1) { @Embeddable public static class Emb implements Serializable { + @Column(name = "val") private String value; public Emb() { diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/embeddable/SetNoEqualsHashCodeTest.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/embeddable/SetNoEqualsHashCodeTest.java index a4f420c605fe..74a7576b6cb5 100644 --- a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/embeddable/SetNoEqualsHashCodeTest.java +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/embeddable/SetNoEqualsHashCodeTest.java @@ -10,6 +10,7 @@ import java.util.HashSet; import java.util.Set; +import javax.persistence.Column; import javax.persistence.ElementCollection; import javax.persistence.Embeddable; import javax.persistence.Entity; @@ -188,6 +189,7 @@ public void setEmbs1(Set embs1) { @Embeddable public static class Emb implements Serializable { + @Column(name = "val") private String value; public Emb() { 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/envers/test/integration/naming/VersionsJoinTableRangeTestEntity.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/naming/VersionsJoinTableRangeTestEntity.java index 31b1e4b9eadd..e590afbe0a21 100644 --- a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/naming/VersionsJoinTableRangeTestEntity.java +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/naming/VersionsJoinTableRangeTestEntity.java @@ -6,6 +6,7 @@ */ package org.hibernate.envers.test.integration.naming; +import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Table; @@ -22,6 +23,7 @@ public class VersionsJoinTableRangeTestEntity extends VersionsJoinTableRangeTestEntitySuperClass { + @Column(name = "val") private String value; /** diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/query/AbstractEntityWithChangesQueryTest.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/query/AbstractEntityWithChangesQueryTest.java index 4a25ef85e425..06c6a14b9bb4 100644 --- a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/query/AbstractEntityWithChangesQueryTest.java +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/query/AbstractEntityWithChangesQueryTest.java @@ -11,6 +11,7 @@ import java.util.Collections; import java.util.List; +import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; @@ -168,6 +169,7 @@ public static class Simple { @GeneratedValue private Integer id; private String name; + @Column(name = "val") private Integer value; Simple() { diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/query/OrderByThreeEntityTest.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/query/OrderByThreeEntityTest.java index 7ba9a33217b1..bbdeed2fadfe 100644 --- a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/query/OrderByThreeEntityTest.java +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/query/OrderByThreeEntityTest.java @@ -12,6 +12,7 @@ import java.util.Map; import java.util.Objects; +import javax.persistence.Column; import javax.persistence.ElementCollection; import javax.persistence.Entity; import javax.persistence.GeneratedValue; @@ -75,6 +76,7 @@ public void setData(Map data) { public static class Key { @Id private Integer id; + @Column(name = "val") private String value; public Key() { @@ -134,6 +136,7 @@ public String toString() { public static class Item { @Id private Integer id; + @Column(name = "val") private String value; public Item() { diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/reventity/MonotonicRevisionNumberTest.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/reventity/MonotonicRevisionNumberTest.java index 6b9274973d96..0da7c9cee19b 100644 --- a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/reventity/MonotonicRevisionNumberTest.java +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/reventity/MonotonicRevisionNumberTest.java @@ -6,6 +6,14 @@ */ package org.hibernate.envers.test.integration.reventity; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Optional; + +import org.hibernate.boot.model.relational.AuxiliaryDatabaseObject; +import org.hibernate.boot.model.relational.Database; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; +import org.hibernate.boot.model.relational.internal.SqlStringGenerationContextImpl; import org.hibernate.dialect.Oracle8iDialect; import org.hibernate.envers.enhanced.OrderedSequenceGenerator; import org.hibernate.envers.enhanced.SequenceIdRevisionEntity; @@ -35,10 +43,18 @@ public void testOracleSequenceOrder() { EntityPersister persister = sessionFactory().getEntityPersister( SequenceIdRevisionEntity.class.getName() ); IdentifierGenerator generator = persister.getIdentifierGenerator(); Assert.assertTrue( OrderedSequenceGenerator.class.isInstance( generator ) ); - OrderedSequenceGenerator seqGenerator = (OrderedSequenceGenerator) generator; + + Database database = metadata().getDatabase(); + SqlStringGenerationContext sqlStringGenerationContext = + SqlStringGenerationContextImpl.forTests( database.getJdbcEnvironment() ); + Optional sequenceOptional = database.getAuxiliaryDatabaseObjects().stream() + .filter( o -> "REVISION_GENERATOR".equals( o.getExportIdentifier() ) ) + .findFirst(); + assertThat( sequenceOptional ).isPresent(); + String[] sqlCreateStrings = sequenceOptional.get().sqlCreateStrings( sqlStringGenerationContext ); Assert.assertTrue( "Oracle sequence needs to be ordered in RAC environment.", - seqGenerator.sqlCreateStrings( getDialect() )[0].toLowerCase().endsWith( " order" ) + sqlCreateStrings[0].toLowerCase().endsWith( " order" ) ); } } diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/strategy/ValidityAuditStrategyComponentCollectionRevEndTest.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/strategy/ValidityAuditStrategyComponentCollectionRevEndTest.java index 55211a4e02e5..253e82c2af5a 100644 --- a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/strategy/ValidityAuditStrategyComponentCollectionRevEndTest.java +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/strategy/ValidityAuditStrategyComponentCollectionRevEndTest.java @@ -12,6 +12,7 @@ import java.util.Map; import javax.persistence.CollectionTable; +import javax.persistence.Column; import javax.persistence.ElementCollection; import javax.persistence.Embeddable; import javax.persistence.Entity; @@ -183,6 +184,7 @@ public boolean equals(Object object) { @Audited public static class Item { private String name; + @Column(name = "val") private Integer value; Item() { diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/superclass/auditoverride/AuditOverrideAuditJoinTableTest.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/superclass/auditoverride/AuditOverrideAuditJoinTableTest.java index 6c9ece5fcb61..e0dec564b6e9 100644 --- a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/superclass/auditoverride/AuditOverrideAuditJoinTableTest.java +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/superclass/auditoverride/AuditOverrideAuditJoinTableTest.java @@ -12,6 +12,7 @@ import java.util.Arrays; import java.util.List; +import javax.persistence.Column; import javax.persistence.ElementCollection; import javax.persistence.Entity; import javax.persistence.Id; @@ -228,6 +229,7 @@ public void setSuperStringList(List superStringList) { @Entity(name = "OOE") @Audited public static class OtherAuditedEntity extends SuperClass { + @Column(name = "val") private String value; @NotAudited private String notAuditedValue; @@ -302,6 +304,7 @@ public void setSuperStringList(List superStringList) { ) }) public static class OtherOverrideAuditedEntity extends NonAuditedSuperClass { + @Column(name = "val") private String value; @NotAudited private String notAuditedValue; @@ -333,6 +336,7 @@ public void setNotAuditedValue(String notAuditedValue) { ) }) public static class OtherAuditParentsAuditEntity extends NonAuditedSuperClass { + @Column(name = "val") private String value; @NotAudited private String notAuditedValue; 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-integrationtest-java-modules/src/test/resources/META-INF/persistence.xml b/hibernate-integrationtest-java-modules/src/test/resources/META-INF/persistence.xml index 810b4f2d9cac..9491fdb51129 100644 --- a/hibernate-integrationtest-java-modules/src/test/resources/META-INF/persistence.xml +++ b/hibernate-integrationtest-java-modules/src/test/resources/META-INF/persistence.xml @@ -14,7 +14,7 @@ - + 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 5ea6db0adf37..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; @@ -52,7 +54,7 @@ public void acquireResources() { .configure( "hibernate-config/hibernate.cfg.xml" ); if ( H2Dialect.class.equals( Dialect.getDialect().getClass() ) ) { - ssrb.applySetting( AvailableSettings.URL, "jdbc:h2:mem:db-mvcc;MVCC=true" ); + ssrb.applySetting( AvailableSettings.URL, "jdbc:h2:mem:db-mvcc" ); } ssrb.applySetting( AvailableSettings.GENERATE_STATISTICS, "true" ); @@ -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/TestHelper.java b/hibernate-jcache/src/test/java/org/hibernate/jcache/test/TestHelper.java index 2020594f4d77..e84c1291437f 100644 --- a/hibernate-jcache/src/test/java/org/hibernate/jcache/test/TestHelper.java +++ b/hibernate-jcache/src/test/java/org/hibernate/jcache/test/TestHelper.java @@ -122,7 +122,7 @@ public static StandardServiceRegistryBuilder getStandardServiceRegistryBuilder() .applySetting( AvailableSettings.HBM2DDL_AUTO, "create-drop" ); if ( H2Dialect.class.equals( Dialect.getDialect().getClass() ) ) { - ssrb.applySetting( AvailableSettings.URL, "jdbc:h2:mem:db-mvcc;MVCC=true" ); + ssrb.applySetting( AvailableSettings.URL, "jdbc:h2:mem:db-mvcc" ); } return ssrb; } 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-osgi/src/test/resources/META-INF/persistence.xml b/hibernate-osgi/src/test/resources/META-INF/persistence.xml index 5f34c3fffa25..3c46d5a3785b 100644 --- a/hibernate-osgi/src/test/resources/META-INF/persistence.xml +++ b/hibernate-osgi/src/test/resources/META-INF/persistence.xml @@ -15,7 +15,7 @@ - + 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/h2geodb/resources/hibernate.properties b/hibernate-spatial/databases/h2geodb/resources/hibernate.properties index a2e07e9cd24a..091f18fd860b 100644 --- a/hibernate-spatial/databases/h2geodb/resources/hibernate.properties +++ b/hibernate-spatial/databases/h2geodb/resources/hibernate.properties @@ -7,7 +7,7 @@ hibernate.dialect org.hibernate.spatial.dialect.h2geodb.GeoDBDialect hibernate.connection.driver_class org.h2.Driver -hibernate.connection.url jdbc:h2:mem:testhbs;DB_CLOSE_DELAY=-1;MVCC=true +hibernate.connection.url jdbc:h2:mem:testhbs;DB_CLOSE_DELAY=-1 hibernate.connection.username sa hibernate.connection.password sa 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 70d610afc06b..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 # @@ -29,7 +30,7 @@ hibernate.connection.password=@jdbc.pass@ ## #hibernate.dialect org.hibernate.spatial.dialect.h2geodb.GeoDBDialect #hibernate.connection.driver_class org.h2.Driver -#hibernate.connection.url jdbc:h2:mem:testhbs;DB_CLOSE_DELAY=-1;MVCC=true +#hibernate.connection.url jdbc:h2:mem:testhbs;DB_CLOSE_DELAY=-1 #hibernate.connection.username sa #hibernate.connection.password sa ## 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/DialectChecks.java b/hibernate-testing/src/main/java/org/hibernate/testing/DialectChecks.java index f2f1b472f1d5..8b2a46b7e11b 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/DialectChecks.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/DialectChecks.java @@ -9,6 +9,7 @@ import org.hibernate.dialect.CockroachDB192Dialect; import org.hibernate.dialect.DB2Dialect; import org.hibernate.dialect.Dialect; +import org.hibernate.dialect.H2Dialect; import org.hibernate.dialect.MySQLDialect; import org.hibernate.dialect.PostgreSQL81Dialect; import org.hibernate.dialect.SybaseDialect; @@ -298,4 +299,9 @@ public boolean isMatch(Dialect dialect) { return dialect.getDefaultMultiTableBulkIdStrategy() instanceof GlobalTemporaryTableBulkIdStrategy; } } + public static class NotH2Version2 implements DialectCheck { + public boolean isMatch(Dialect dialect) { + return !( dialect instanceof H2Dialect ) || !( (H2Dialect) dialect ).isVersion2(); + } + } } 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/env/TestingDatabaseInfo.java b/hibernate-testing/src/main/java/org/hibernate/testing/env/TestingDatabaseInfo.java index 920bfa95ec24..47a828664a3f 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/env/TestingDatabaseInfo.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/env/TestingDatabaseInfo.java @@ -16,7 +16,7 @@ */ public final class TestingDatabaseInfo { public static volatile String DRIVER = "org.h2.Driver"; - public static volatile String URL = "jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1;MVCC=TRUE"; + public static volatile String URL = "jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1"; public static volatile String USER = "sa"; public static volatile String PASS = ""; 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/migration-guide.adoc b/migration-guide.adoc index fe220a2b9b41..c9bb6396d733 100644 --- a/migration-guide.adoc +++ b/migration-guide.adoc @@ -19,3 +19,65 @@ configure `hibernate.bytecode.provider=javassist`: remove the property if you're A side effect is that Hibenate ORM no longer lists javassist among its dependencies. +=== Changes to the DDL type for CLOB in PostgreSQL81Dialect and its subclasses + +As of 5.6.2, the default PostgreSQL DDL type for CLOB columns i.e. fields annotated with `@Lob` or with the type `java.sql.Clob` +will be the `oid` type whereas before, the type `text` was used. The `text` type does not support streaming data +and is, even if TOASTed, materialized eagerly by the server, which is not what one would expect for LOB types. + +All PostgreSQL JDBC drivers unfortunately just store the `oid` it created for a `java.sql.Clob` into the `text` column. +Although reading back the value with the CLOB API works, PostgreSQL has no knowledge of the reference to the LOB, +because the `oid` is not known to PostgreSQL, leading to data loss when `vacuumlo` (the utility to clean up unused LOBs) runs. +To avoid the data loss, it is required to use the `oid` type so that `vacuumlo` can see the reference. + +Updating to 5.6.2 does not require any schema or application changes by default, but we highly recommend +that you migrate existing `text` columns for LOBs to `oid` to prevent data loss due to the activity of `vacuumlo`. + +[source,sql] +---- +alter table test_entity +alter column clobfield +set data type oid using clobfield::oid +---- + +If you are overriding the `JdbcTypeDescriptor` for `CLOB` to use e.g. `VarcharTypeDescriptor` in a custom PostgreSQL dialect, +beware that you will also have to override the column type in the custom dialect, as with "pgjdbc", +it is not possible to read/write an `oid` column with the JDBC `ResultSet#getString/Statement#setString` methods. + +[source,java] +---- +registerColumnType( Types.CLOB, "text" ); +---- + +Alternatively, you can remove the `JdbcTypeDescriptor` override and migrate to `oid` with + +[source,sql] +---- +alter table test_entity +alter column clobfield +set data type oid using lo_from_bytea(0, cast(clobfield as bytea)) +---- + +The switch to `oid` might have a negative impact on performance for small values that are fetched often, +because the value is stored in a different file system page than the row, even for small values +The benefit of the `oid` type is that it allows streaming the content and reduces the row size. + +Users that just want a large text type but don't care about streaming should use the Hibernate type `text`: + +[source,java] +---- +@Entity +public class TestEntity { + + @org.hibernate.annotations.Type( type = "text" ) + String clobField; + + //... +} +---- + +This will map to `java.sql.Types.LONGVARCHAR` for which Hibernate dialects register a DDL type that supports access +via the `ResultSet#getString/Statement#setString` methods i.e. in case of PostgreSQL the type `text`. + +The `@Lob` annotation should only be used to force the use of the `ResultSet#getClob/Statement#setClob` JDBC driver methods, +which is in turn necessary for streaming data. diff --git a/release/release.gradle b/release/release.gradle index 7c95227d6879..618b716c30c4 100644 --- a/release/release.gradle +++ b/release/release.gradle @@ -108,10 +108,10 @@ task assembleDocumentation(type: Task, dependsOn: [rootProject.project( 'documen task uploadDocumentation(type:Exec, dependsOn: assembleDocumentation) { description = "Uploads documentation to the JBoss doc server" - final String url = "filemgmt.jboss.org:/docs_htdocs/hibernate/orm/${rootProject.ormVersion.family}"; + final String url = "filemgmt-prod-sync.jboss.org:/docs_htdocs/hibernate/orm/${rootProject.ormVersion.family}"; executable 'rsync' - args '-avz', '--links', '--protocol=28', "${documentationDir.absolutePath}/", url + args '--port=2222', '-avz', '--links', "${documentationDir.absolutePath}/", url doFirst { if ( rootProject.ormVersion.isSnapshot ) { @@ -128,163 +128,20 @@ task uploadDocumentation(type:Exec, dependsOn: assembleDocumentation) { } } - -/** - * 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" ) +task releasePerform( dependsOn: [releaseChecks, uploadDocumentation] ) { + doLast { + String tag = null + if ( !project.hasProperty( 'noTag' ) ) { + logger.lifecycle("Pushing branch to remote '${project.gitRemote}'...") + executeGitCommand( 'push', project.gitRemote , project.gitBranch ) } else { - logger.lifecycle( "Uploading release bundles to SourceForge..." ) + logger.lifecycle("Pushing branch and tag to remote '${project.gitRemote}'...") + executeGitCommand( 'push', '--atomic', project.gitRemote , project.gitBranch, tag ) } } - - 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" @@ -307,18 +164,16 @@ task addVersionCommit( dependsOn: [changeLogFile] ) { 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 ) + if ( subProject.tasks.findByName( 'releasePerform' ) ) { + this.tasks.releasePerform.dependsOn( subProject.tasks.releasePerform ) } } } -task ciRelease( dependsOn: [releaseChecks, addVersionCommit, release] ) { +task releasePrepare( dependsOn: [releaseChecks, addVersionCommit, 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 { @@ -330,22 +185,13 @@ task ciRelease( dependsOn: [releaseChecks, addVersionCommit, release] ) { tag = tag.replace( ".Final", "" ) } logger.lifecycle( "Tagging '${tag}'..." ) - executeGitCommand( 'tag', tag ) + executeGitCommand( 'tag', '-a', tag, '-m', "Release $project.ormVersion.fullName" ) } logger.lifecycle( "Adding commit to update version to '${project.developmentVersion}'..." ) 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 ) - } } } 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